c# WPF MVVM Set View Property From ViewModel - c#

I want to create a simple Control to enter Text.
For this Control i want to create a Property.
This Property should be bind to the ViewModel.
I Created a Model for an Folder:
public class FolderModel : ModelBase
{
private string fullPath;
public string FullPath
{
get { return fullPath; }
set
{
if (fullPath == value)
return;
fullPath = value;
RaisePropertyChanged(nameof(FullPath));
}
}
}
In my View xaml i bound the Data to my ViewModel:
<UserControl.DataContext>
<Browser:FolderBrowserViewModel/>
</UserControl.DataContext>
<Grid>
<TextBox x:Name="TextBox_Folder" IsReadOnly="True" Text="{Binding Folder.FullPath}"/>
</Grid>
To Create the Property created this code in the View:
public string FullPath
{
get { return (string)GetValue(FolderFullPathProperty); }
set { SetValue(FolderFullPathProperty, value); }
}
public static readonly DependencyProperty FolderFullPathProperty = DependencyProperty.Register("FullPath", typeof(string), typeof(FolderBrowser), new PropertyMetadata("", new PropertyChangedCallback(OnSetTextChanged)));
private static void OnSetTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FolderBrowser UserControl1Control = d as FolderBrowser;
UserControl1Control.OnSetTextChanged(e);
}
private void OnSetTextChanged(DependencyPropertyChangedEventArgs e)
{
TextBox_Folder.Text = e.NewValue.ToString();
}
My ViewModel looks like this:
private ICommand _browseFolderCommand;
public ICommand BrowseFolderCommand
{
get
{
return _browseFolderCommand is null ? (_browseFolderCommand = new RelayCommand(() => BrowseFolder(), true)) : _browseFolderCommand;
}
}
private FolderModel folder = new FolderModel();
public FolderModel Folder
{
get { return folder; }
set
{
if (folder == value)
return;
folder = value;
}
}
private void BrowseFolder()
{
System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog();
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
SetField(ref folder, Folder, "FullPath");
}
}
and my ViewModelBase looks like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected void Dispatch(Action f) => Application.Current.Dispatcher.Invoke(f);
protected TResult Dispatch<TResult>(Func<TResult> f) => Application.Current.Dispatcher.Invoke(f);
}
if i use the Control i want to set the Property "FullPath"
when i set this Property the Value should be set to the ViewModels Folder.FullPath.
If this is set, the Binding of the TextBox and Folder.FullPath should show the correct path.
if its possible i want all Code in the ViewModel =)
Edit:
I tried to illustrate it a bit more pictorially:
On the left side you can see the control as it is placed on a window. There you can set MyProp to any value and the TextBox will receive it.
On the right side I have tried to show it in more detail.
The view has the label, a TextBox and a button.
The view also has the property "MyProp".
The textbox of the view is bound to the ViewModel, namely to the field "MyCoolFieldValue".
This means that if I do anything in the ViewModel with the field MyCoolFieldValue, I know that it will always have the value that is in the textbox.
If I now press the button, a command is called. This command changes the WErt of MyCoolFieldValue. When this happens, the value should be written directly back into the property and the textbox of the view.
However, I can't get this to work and have tried it with the code above.

Related

WPF, MVVM, how to bind propertys dynamically?

I have a question. How to bind variables dynamically from ViewModel to View? For now, it not even displayed. If i not using Command, it works great (but of course, i can bind image only once).
My View:
namespace somestuff.View
{
public partial class WindowView : Window
{
public WindowView()
{
this.DataContext = new WindowViewModel();
InitializeComponent();
}
}
}
my View.Xaml (shorten):
<Image Source="{Binding DisplayedImage}"/>
<Button Command="{Binding NewImageCommand}"/>
And my ViewModel:
public WindowViewModel()
{
_canExecute = true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string DisplayedImage //displaying image
{
get { return filepath; }
set { filepath = value; NotifyPropertyChanged(nameof(filepath)); }
}
public string filepath { get; set; } //var for binding
private bool _canExecute;
private ICommand _newImageCommand; //command for button
public ICommand NewImageCommand
{
get
{
return _newImageCommand ?? (_newImageCommand = new Commands.CommandHandler(() => GetImage(), _canExecute));
}
}
public void GetImage() { filepath = Pictures.GetNewImage(); } //command on button click
Can you tell me, why after triggering Command GetImage() on button click the image binded on Image not changed? If i move filepath = Pictures.GetNewImage(); from command (more clear, i not use command) all works great, but i cant re-invoke binding to my Image. Can you tell me, how to bind propertis dynamically into View from View model? When value of variable (in this case, filepath) change, i want to change View control too.
Thanks for any advices.
EDIT:
I have 6 Image Labels. I displaying images in it like that:
public BitmapImage DisplayedHighPerformanceImage
{
get { return kMMHP; }
set { kMMHP = value; NotifyPropertyChanged(nameof(kMMHP)); }
}
So i need filepath to init 6 diffrent bitmaps. Then i work on that bitmaps (for exampe, that kMMHP) So i want to display every new bitmap initialized from kMMHP image.
kMMHP = method1(); //displaying it
//other stuff do with diffrent bmps
kMMHP = method2(); //displaying it after second method with changed values
NotifyPropertyChanged must be called with the name of the property, not the name of its backing field. And in order to fire the change notification event, you have to set the property, not the backing field:
public BitmapImage DisplayedHighPerformanceImage
{
get { return kMMHP; }
set { kMMHP = value; NotifyPropertyChanged(nameof(DisplayedHighPerformanceImage)); }
}
DisplayedHighPerformanceImage = method1();

MVVM DataBinding OK in Designer, not at runtime

Why can databinding be seen working in the designer:
Click to show image: Databinding seems OK
But runtime shows nothing?
Click to show image: No Data, no usercontrol?
Outline code structure:
ViewModelBase : baseclass inheriting from INotofyPropertychanged
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
}
SiteViewModel : Model class with Id/Name/Description Properties
public class SiteViewModel : ViewModelBase
{
private int _SiteID;
private string _Name;
private string _Description;
public int SiteID
{
get { return _SiteID; }
set { SetProperty(ref _SiteID, value); }
}
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
public string Description
{
get { return _Description; }
set { SetProperty(ref _Description, value); }
}
}
SitesViewModel: ObservableCollection of SiteViewModel
public class SitesViewModel : ViewModelBase
{
private ObservableCollection<SiteViewModel> _AllSites;
public ObservableCollection<SiteViewModel> AllSites {
get { return _AllSites; }
set { SetProperty<ObservableCollection<SiteViewModel>>(ref _AllSites, value); }
}
public SitesViewModel()
{
AllSites = new ObservableCollection<SiteViewModel>();
for (int count = 1; count <= 3; count++)
{
AllSites.Add(new SiteViewModel { SiteID = count, Name = "Test" + count.ToString(), Description = "Site:" + count.ToString() } );
}
}
}
SiteManagerControl : UserControl with a SitesViewModel property _AllSites
public partial class SiteManagerControl : UserControl
{
private SitesViewModel _AllSites;
public SitesViewModel AllSites
{
get { return _AllSites; } //<-- Breakpoint not hit!
set {
if (_AllSites != value)
{ _AllSites = value;
OnPropertyChanged("AllSites");
}}
}
public SiteManagerControl(){
_AllSites = new SitesViewModel();}
(XAML can be seen in the first linked image above, Note the breakpoint not hit line in the above). The user control is hosted in a Tabcontrol that is part of an ObservableCollection. I don't think this is an issue in the databinding. Will post the code for the tabs if needed.
There are no errors in the Debug Output window to indicate why the databinding is failing.
Your listview DataContext Data binding is with object from class(SitesViewModel)
This class has property named (AllSites) that has oveservable collection property named (AllSites) as well.
so I think you have to fix the ItemSource binding in the list view like this:
ItemsSource="{Binding AllSites.AllSites}"
Will's comment (thanks!) above pointed me in the right direction: Changed MainWindow.xaml to contain:
<DataTemplate DataType="{x:Type vm:SitesViewModel}">
<uc:SitesView></uc:SitesView>
</DataTemplate>
Also followed this : http://codingtales.blogspot.co.uk/2010/02/creating-complete-tabbed-interface-in.html to rework my tab interface

Binding variable to a textblock in Windows Phone 8

In XAML, i have a textblock
<TextBlock x:Name="block" Text="{Binding b1}"/>
and in c# i created a property
public int _b1;
public int b1
{
get { return _b1; }
set
{
_b1 = value;
}
}
public MainPage()
{
InitializeComponent();
block.DataContext = this;
}
this worked fine, textblock show the _b1. But when i add a button to chage the _b1 variable
private void bt_click(object sender, RoutedEventArgs e)
{
_b1 = 4;
}
the textblock didn't update ?????
To add to dotNet's answer (which is the correct answer), use a baseclass where you implement INotifyPropertyChanged if you want to avoid redundand code: (this is one example, there are other ways to implement this)
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value)) { return false; }
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And use it like so:
class MyClass: BindableBase
{
private int _b1;
public int B1
{
get { return _b1; }
set { SetProperty(ref _b1, value); }
}
}
For UI to update automatically upon property value change, your property needs to either be a DependencyProperty or your class needs to implement INotifyPropertyChanged interface.
For creating a DependencyProperty, you could use Visual Studio's propdp snippet (type propdp inside your class and press Tab) and fill in respective values. If you want to go INotifyPropertyChanged path, you'll need to write the following code in the setter of your property (AFTER setting the value of _b1):
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("b1"));

Can Indexers of a property work in xaml binding in windows phone 8?

something i do wrong? anyone gives some suggestions
according to msdn
Indexers of a property can be specified within square brackets following the property name where the indexer is applied. For instance, the clause Path=ShoppingCart[0] sets the binding to the index that corresponds to how your property's internal indexing handles the literal string "0". Multiple indexers are also supported.
i put Indexers of a property in my xaml
<Image Source="{Binding ImagePathList[0]}" Height="50" Width="50" Grid.Row="0" Grid.Column="0" VerticalAlignment="Top" Margin="0,7,7,0" Grid.RowSpan="2">
i do not give the viewmodel code because i am pretty sure ListImagePathList have data.
EDIT*
more detail: ImagePathList[0] is a web image url
EDIT FOR Brendan
model is Article
public class Article : INotifyPropertyChanged
{
private long _Id;
public long ID
{
get { return _Id; }
set
{
if (_Id != value)
{
_Id = value;
NotifyPropertyChanged();
}
}
}
private string _subject;
public string Subject
{
get
{
return _subject;
}
set
{
if (_subject != value)
{
_subject = value;
NotifyPropertyChanged();
}
}
}
private string _words;
public string Words
{
get
{
return _words;
}
set
{
if (_words != value)
{
_words = value;
NotifyPropertyChanged();
}
}
}
private DateTime _publishDate;
public DateTime PublishDate
{
get
{ return _publishDate; }
set
{
if (_publishDate != value)
{
_publishDate = value;
NotifyPropertyChanged();
}
}
}
public List<string> ImagePathList = new List<string>();
private string _firstImage;
public string FirstImage
{
get
{
return _firstImage;
}
set
{
if (_firstImage != value)
{
_firstImage = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ArticleViewModel is in below; All data returned from network is correct!
public class ArticleListViewModel : INotifyPropertyChanged
{
public ArticleListViewModel()
{
this.ArticleCollection = new ObservableCollection<Article>();
}
public ObservableCollection<Article> ArticleCollection
{
get;
private set;
}
public void LoadPage(int pageNumber)
{
if (pageNumber == 1)
{
this.ArticleCollection.Clear();
}
IsLoading = true;
ReadArticleList(pageNumber);
}
private async void ReadArticleList(int pageNumber)
{
try
{
List<Article> articleList = new List<Article>();
articleList = await CollectionHttpClient.GetArticlesByPageAsync(pageNumber);
this.ArticleCollection.Add(item);
}
}
catch(Exception ex)
{
if (ex.HResult == -2146233088 && ex.Message.Equals("Response status code does not indicate success: 404 ()."))
{
MessageBox.Show("The network is not set right. Internet cannot be accessed.");
}
else
{
MessageBox.Show("sorry, no data.");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The XAML code you show is fine.
There may be a problem with the DataContext. Maybe the page DataContext has not been set? Or maybe the the DataContext has changed e.g. inside an ItemTemplate
Otherise the problem is probably to do with the bound property. Try the following
private ObservableCollection<string> _imagePathList = new ObservableCollection<string>();
public ObservableCollection<string> ImagePathList {
get { return this._imagePathList; }
set {
if (this._imagePathList != value)
{
this._imagePathList = value;
// I'm going to assume you have the NotifyPropertyChanged
// method defined on the view-model
this.NotifyPropertyChanged();
}
}
}
ObservableCollection is in System.Collections.ObjectModel and is like List but if elements are added/removed then the PropertyChanged event is fired. Also note that any bound property must have a get associated with it for it to work at all.
Another possibility is that ImagePathList was not assigned to or is empty - in which case, make sure you assign to it!
In case you have not yet implemented the NotifyPropertyChanged method, here it is ...
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You will also need to add INotifyPropertyChanged interface to the containing class e.g.
public class MyViewModelClass : INoftifyPropertyChanged
{
...
}
I think the problem concerns the place where your images come from. As it is said at MSDN:
You can set the Source by specifying an absolute URL (e.g. http://contoso.com/myPicture.jpg) or specify a URL relative to the XAP file of your application.
You can set this property in XAML, but in this case you are setting the property as a URI. The XAML behavior relies on underlying type conversion that processes the string as a URI, and calls the BitmapImage(Uri) constructor. This in turn potentially requests a stream from that URI and returns the image source object.
Just for test - place some images in your project and then set ImagePathList to them. See if that works (it should as I think). Or see if you can get BitmapImage(ImagePathList);
It's hard for me to say now (as I don't know how your ImagePathList looks like) what could be a reason of the failure. But if I were you, I would test this.
I would advise to use a property (or you can use converter which will also do the job):
// a property in your class
public Uri FirstImage
{
get
{
return new Uri(ImagePathList.FirstOrDefault(), UriKind.Absolute);
}
}
On the other hand if you are downloading images to IsolatedStorage then you will have to use another property, that will load BitmapImage from IS:
public BitmapImage FirstImage // getter - BitmapImage
{
get
{
if (String.IsNullOrEmpty(ImagePath[0])) return null;
BitmapImage temp = new BitmapImage();
using (IsolatedStorageFile ISF = IsolatedStorageFile.GetUserStoreForApplication())
using (IsolatedStorageFileStream file = ISF.OpenFile(ImagePath[0], FileMode.Open, FileAccess.Read))
temp.SetSource(file);
return temp;
}
}
Hope this helps.

How to Pass a Value From a Window to a UserControl in WPF

I want to pass a value from MainWindow into my UserControl! I passed a value to my UserControl and the UserControl showed me the value in a MessageBox, but it is not showing the value in a TextBox. Here is my code:
MainWindow(Passing Value To UserControl)
try
{
GroupsItems abc = null;
if (abc == null)
{
abc = new GroupsItems();
abc.MyParent = this;
abc.passedv(e.ToString(), this);
}
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
UserControl
public partial class GroupsItems : UserControl
{
public MainWindow MyParent { get; set; }
string idd = "";
public GroupsItems()
{
InitializeComponent();
data();
}
public void passedv(string id, MainWindow mp)
{
idd = id.ToString();
MessageBox.Show(idd);
data();
}
public void data()
{
if (idd!="")
{
MessageBox.Show(idd);
texbox.Text = idd;
}
}
}
EDIT(using BINDING and INotifyProperty )
.....
public GroupsItems()
{
InitializeComponent();
}
public void passedv()
{
textbox1.Text = Text;
}
}
public class Groupitm : INotifyPropertyChanged
{
private string _text = "";
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Problem here is with reference.
When you create new object in code behind, new object will be created and this is not the same object which you have in xaml code. So you should use following code:
<local:GroupsItems x:Name="myGroupsItems"/>
and in code behind you don't have to create new object. You should use object that you added in XAML:
...
myGroupsItems.MyParent = this;
myGroupsItems.passedv(e.ToString(), this);
...
Here is example solution (sampleproject).
You are calling data in the constructor when idd is still "" which results in the text box still being empty. Changing the MyParent property does not change that. Only passedv does. But at that point you do not have the parent set. Just call data in passedv, too.
Try this:
public partial class GroupsItems : UserControl
{
//properties and methods
private string idd="";
public string IDD
{
get{return idd;}
set{
idd=value;
textBox1.Text=idd;
}
}
//other properties and methods
}
Usage:
In your Main form:
abc = new GroupsItems();
abc.IDD="sometext";
MainGrid1.Children.Add(abc); //Grid or any other container for your UserControl
In your Binding example, your GroupItem class looks ok, except that you need to pass in the name of the changed property:
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged("Text");
}
}
}
Now, in GroupsItems, you shouldn't be accessing the TextBox. In WPF, we manipulate the data, not the UI... but as we use Binding objects to data bind the data to the UI controls, they automatically update (if we correctly implement the INotifyPropertyChanged interface).
So first, let's add a data property into your code behind (which should also implement the INotifyPropertyChanged interface) like you did in your GroupItem class:
private GroupItem _item = new GroupItem();
public GroupItem Item
{
get { return _item; }
set
{
if (value != _item)
{
_item = value;
NotifyPropertyChanged("Item");
}
}
}
Now let's try using a Binding on a TextBox.Text property:
<TextBox Text="{Binding Item.Text}" />
See how we bind the Text property of your GroupItem class to the TextBox.Text property... now all we need to do is to change the value of the Item.Text property and watch it update in the UI:
<Button Content="Click me" Click="Button_Click" />
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Item.Text = "Can you see me now?";
}
Alternatively, you could put this code into your passedv method if you are calling that elsewhere in your project. Let me know how you get on.
UPDATE >>>
In your GroupItem class, try changing the initialization to this:
private string _text = "Any text value";
Can you now see that text in the UI when you run the application? If not, then try adding/copying the whole Text property into your code behind and changing the TextBox declaration to this:
<TextBox Text="{Binding Text}" />
If you can't see the text value now, you've really got problems... you have implemented the INotifyPropertyChanged interface in your code behind haven't you?

Categories