Binding ViewModel property to a View - c#

I have the following (a base class for pages and a viewmodel class):
public class MySuperPage : Page {
public MySuperPageViewModel VM = new MySuperPageViewModel();
..........
..........
public class MySuperPageViewModel {
protected bool _ShowProgress = false;
public bool ShowProgress {
get { return _ShowProgress; }
set {
_ShowProgress = value;
OnPropertyChanged("ShowProgress");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property) {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Then an actual page
public class MyPage : MySuperPage(){
public MyPage() : base() {
this.InitializeComponent();
}
}
The XAML is the following:
<my:MySuperPage
xmlns:my="using:MyNamespace"
x:Name="pageRoot"
x:Class="MyPages.MyPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
....>
<ProgressRing IsActive="{Binding VM.ShowProgress, ElementName=pageRoot}" ... />
If in the code-behind I do actions such as
this.VM.ShowProgress = true; // or false
the effects are not visible.
Instead, things work if I assign object 'VM' to DefaultViewModel (which is an ObservableCollection):
this.DefaultViewModel["VM"] = VM;
I.e., in this last case using {Binding DefaultViewModel.VM.ShowProgress, ElementName=pageRoot} I manage to have the progress ring reflect the state of the VM instance.
I feel to miss something.

Your ViewModel in your page must be implemented as a property to make the binding work.
Change
public MySuperPageViewModel VM = new MySuperPageViewModel();
to
private MySuperPageViewModel _vm = new MySuperPageViewModel();
public MySuperPageViewModel VM { get { return _vm; }}

Related

DataTemplate's binding gives the target an incorrect/unset/null value

I would like to propagate DataContext upwards from a dynamically created DataTemplate hosted withing a ContentControl such as following one:
var myFactory = new FrameworkElementFactory(controlTypeToDisplay);//= typeof(MyControl)
var dctxBinding = new Binding
{
Path = new PropertyPath("DataContext.Dctx"),
Mode = BindingMode.OneWayToSource,
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ContentControl), 1)
};
myFactory.SetBinding(FrameworkElement.DataContextProperty, dctxBinding);
return new DataTemplate() { VisualTree = myFactory };
However the result of such binding is null even though the DataContext is being set within MyControl's constructor. DataContext of MyControl definitely is not set null further down the road, constructor is invoked before setter of Dctx. How can I fix the binding so that MyControl's DataContext and Dctx property are always in sync?
Full minimal example of the issue (should display two "FooBar" TextBlocks if working correctly):
//MyControl.xaml
<Grid>
<TextBlock Text="{Binding}"/>
</Grid>
//MyControl.xaml.cs
public MyControl()
{
InitializeComponent();
DataContext = "FooBar";
this.DataContextChanged += MyControl_DataContextChanged;
}
private void MyControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("An unexpected change");
}
//MainWindow.xaml
<StackPanel>
<ContentControl ContentTemplate="{Binding DataTemplate}"/>
<TextBlock Text="{Binding Dctx, TargetNullValue='<null>'}" />
</StackPanel>
//MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private Type controlTypeToDisplay = typeof(MyControl);
public DataTemplate DataTemplate
{ get {/*see first listing*/ } }
private object _dctx;
public object Dctx
{
get { return _dctx; }
set { _dctx = value; RaisePropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string caller = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
}
The DataContext of the root element in the ContentTemplate of a ContentControl is the ContentControl's Content. Bind the Content property to your DataContext:
<ContentControl Content="{Binding Dctx}" ContentTemplate="{Binding DataTemplate}"/>
You must also set the DataContext of the ContentControl itself (or the parent window) somewhere, and add some visual element to the DataTemplate:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public DataTemplate DataTemplate
{
get {
var myFactory = new FrameworkElementFactory(typeof(TextBlock));
myFactory.SetBinding(TextBlock.TextProperty, new Binding(".")); //bind to the DataContext
return new DataTemplate() { VisualTree = myFactory };
}
}
private object _dctx = new object(); //set the Dxtc property somewhere
public object Dctx
{
get { return _dctx; }
set { _dctx = value; RaisePropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string caller = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
}

Data Binding Issue: How do I get the Binding value to work both ways?

I am working on a MVVM WPF application and I have a CheckBox which I am trying to work on. What I want is for the value to be binding to a model property (which I have done). However, when I click it in a debugging session it never actually changes my IsChecked property to true from its default false. Please see code below:
Model
public class MyModel:INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked == value)
return;
_isChecked = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
View
<StackPanel Orientation="Horizontal" Height="51" Width="667" Canvas.Left="10" Canvas.Top="45">
<CheckBox IsChecked="{Binding IsChecked}" Command="{Binding CheckBoxClickCommand}" Content="We're in the matrix" VerticalAlignment="Center" Margin="10,10,200,10"/>
</StackPanel>
ViewModel
public class MyViewModel
{
private MyModel _myModel = new MyModel();
public ObservableCollection<MyModel> UrlsList { get; } = new ObservableCollection<MyModel>();
public ICommand CheckBoxClickCommand { get; private set; }
public MyViewModel()
{
CheckBoxClickCommand = new RelayCommand(CheckBoxOnClick);
}
public void CheckBoxOnClick()
{
var newList = new List<MyModel>();
if (_myModel.IsChecked)
{
foreach (var url in UrlsList)
{
if (!url.ExistsInDb)
newList.Add(url);
}
}
}
}
When I debug and get to the if statement in CheckBoxClickCommand it obviously goes to the model to get the property value, but it does not change from the default false to true. Any help is much appreciated, thanks!.
Bind to the model's property:
<CheckBox IsChecked="{Binding Model.IsChecked}" ...>
For this to work, the model has to be returned from a public property of the view model:
private MyModel _myModel = new MyModel();
public MyModel Model { get { return _myModel; }}

Create a binding between properties

I have a Listbox where it's items are objects. In these objects I store two colors.
I want to bind these colors with an other object's property, but how can I achieve this?
The listbox looks like this:
Listbox1.Items.Add(new ColorAndMoreClass(Color.Red, Color.Blue));
Far away, in an other class there is a property which I'd like to bind to.
How can I do that?
Your rootclass could look like this.
In the class you have a object representing a different Class.
public class ColorAndMoreClass: INotifyPropertyChanged
{
private Color _c;
private Color _c2;
private OtherClass _example;
public ColorAndMoreClass(Color c, Color c2)
{
_c= c;
_c2 = c2;
}
public OtherClass example
{
get { return _example }
set
{
_example = value;
OnPropertyChanged("example");
}
}
public Color c
{
get { return _c; }
set
{
_c= value;
OnPropertyChanged("c");
}
}
public Color c2
{
get { return _c2; }
set
{
_c2 = value;
OnPropertyChanged("c2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
Your other class could look like this. I just took a simple string.
public class OtherClass : INotifyPropertyChanged
{
private String _someOtherProperty;
public OtherClass(){}
public String someOtherProperty
{
get { return _someOtherProperty; }
set
{
_someOtherProperty= value;
OnPropertyChanged("someOtherProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
In your Code behind make a property the Listbox can bind to
public List<ColorAndMoreClass>> ListOfColorAndMore{ get; set; }
public Window1()
{
ListOfColorAndMore = GetDataThatFillsUpTheProperty();
InitializeComponent();
DataContext = this;
}
Your XAML could then look like this. The Datatemplate is used to tell XAML how to display your object.
<Grid>
<ListBox ItemsSource={Binding ListOfColorAndMore}>
<DataTemplate x:Key="myTaskTemplate">
<StackPanel>
<TextBlock Text="{Binding Path=c.R}" />
<TextBlock Text="{Binding Path=c2.R}"/>
<TextBlock Text="{Binding Path=example.someOtherProperty}"/>
</StackPanel>
</DataTemplate>
</ListBox>
</Grid>
I hope it is this that you mean. But your question is not that clear.

Using commands in the Data Model scope

In my program I am trying to write commands for a User Control that will toggle the isEnabled and isChecked property of a few controls. Attached to my User Control is a View Model and a Data Model. My commands and properties are in my Data Model (First of all, is this correct implementation?), and there is a property for my Data Model inside my View Model.
The commands are not working. I do not get any binding errors, and when I debug my code, the values are changed correctly. However, there is no visual feedback.
My View Model is set as the DataContext of the User Control in it's constructor.
My data is bound like this:
<CheckBox Command="{Binding Model.myCommand}" ... />
This is an example of what one of my commands looks like:
public Command myCommand { get { return _myCommand; } }
private void MyCommand_C()
{
if (_myCommand== true) //Checked
{
_checkBoxEnabled = true;
}
else //UnChecked
{
_checkBoxEnabled = false;
_checkBox = false;
}
}
Why aren't these commands functioning?
Commands should be implemented in the ViewModel.
There or in your Models, you should have Properties binded to the IsChecked and IsEnabled properties of your controls, and in the command, changing the properties will trigger PropertyChanged event which will update your views.
Example:
In your view :
<StackPanel>
<Button Command="{Binding ToggleCommand}"/>
<CheckBox IsChecked="{Binding Path=Model.IsChecked}"/>
<CheckBox IsEnabled="{Binding Path=Model.IsEnabled}"/>
</StackPanel>
ViewModel:
public class MainWindowViewModel : NotificationObject
{
public MainWindowViewModel()
{
Model = new MyModel();
ToggleCommand = new DelegateCommand(() =>
{
Model.IsChecked = !Model.IsChecked;
Model.IsEnabled = !Model.IsEnabled;
});
}
public DelegateCommand ToggleCommand { get; set; }
public MyModel Model { get; set; }
}
Model:
public class MyModel : INotifyPropertyChanged
{
private bool _isChecked;
private bool _isEnabled;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
_isChecked = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public bool IsEnabled
{
get
{
return _isEnabled;
}
set
{
_isEnabled = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsEnabled"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Hope this helps
First, your Command properties should be in your ViewModel, not your data model.
Setting that aside, you should not bind a CheckBox to a Command - commands are for elements that trigger actions (such as clicking a Button). A CheckBox should be bound to a bool property. Where the property should reside can be debated, but my opinion is that it should be in the ViewModel so you can keep the Property Changed Notification logic out of your data model.
A quick example:
public class MyViewModel : INotifyPropertyChanged
{
private bool _myCheckValue;
public bool MyCheckValue
{
get { return _myCheckValue; }
set
{
_myCheckValue = value;
this.RaisePropertyChanged("MyCheckValue");
}
}
//INotifyPropertyChange implementation not included...
}
And then in your XAML (assuming the ViewModel is the DataContext):
<CheckBox IsChecked="{Binding MyCheckValue}" ... />

WPF MVVM two-way updates

I'm trying to setup a working two-way update by using this example.
These are the relevant code snippets:
XAML:
<Button Click="clkInit">Initialize</Button>
<Button Click="clkStudent">Add student</Button>
<Button Click="clkChangeStudent">Change students</Button>
(...)
<TabControl Name="tabControl1" ItemsSource="{Binding StudentViewModels}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=StudentFirstName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Label Content="First Name" Name="label1" />
<TextBox Name="textBoxFirstName" Text="{Binding Path=StudentFirstName}" />
<Label Content="Last Name" Name="label2" />
<TextBox Name="textBoxLastName" Text ="{Binding Path=StudentLastName}" />
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Main Window:
public partial class MainWindow : Window
{
internal MainWindowViewModel myMWVM;
public MainWindow()
{
InitializeComponent();
}
private void clkInit(object sender, RoutedEventArgs e)
{
myMWVM= new MainWindowViewModel();
DataContext = myMWVM;
}
private void clkStudent(object sender, RoutedEventArgs e)
{
myMWVM.StudentViewModels.Add(new StudentViewModel());
}
// For testing - call a function out of the student class to make changes there
private void clkChangeStudent(object sender, RoutedEventArgs e)
{
for (Int32 i = 0; i < test.StudentViewModels.Count; i++)
{
myMWVM.StudentViewModels.ElementAt((int)i).changeStudent();
}
}
}
Main view:
class MainWindowViewModel : INotifyPropertyChanged
{
ObservableCollection<StudentViewModel> _studentViewModels =
new ObservableCollection<StudentViewModel>();
// Collection for WPF.
public ObservableCollection<StudentViewModel> StudentViewModels
{
get { return _studentViewModels; }
}
// Constructor. Add two stude
public MainWindowViewModel()
{
_studentViewModels.Add(new StudentViewModel());
_studentViewModels.Add(new StudentViewModel());
}
// Property change.
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Student view:
class StudentViewModel : INotifyPropertyChanged
{
Lazy<Student> _model;
string _studentFirstName;
public string StudentFirstName
{
get { return _studentFirstName; }
set
{
if (_studentFirstName != value)
{
_studentFirstName = value;
_model.Value.StudentFirstName = value;
OnPropertyChanged("StudentFirstName");
}
}
}
string _studentLastName;
public string StudentLastName
{
get { return _studentLastName; }
set
{
if (_studentLastName != value)
{
_studentLastName = value;
_model.Value.StudentLastName = value;
OnPropertyChanged("StudentLastName");
}
}
}
public void changeStudent()
{
_model.Value.changeStudent();
}
public StudentViewModel()
{
_studentFirstName = "Default";
_model = new Lazy<Student>(() => new Student());
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
THE student:
class Student
{
public string StudentFirstName { get; set; }
public string StudentLastName { get; set; }
public Student()
{
MessageBox.Show("Student constructor called");
}
public Student(string nm)
{
StudentLastName = nm;
}
public void changeStudent()
{
StudentLastName = "McDonald";
}
}
If you read until here I already thank you :) Still, by calling "clkChangeStudent" I don't see the changes in the textbox. I guess it's because I don't call the set-method of the StudentViewModel. The project I'm working on is a bit complex and a lot of things happen in the class (here Student) itself.
How can I get a textbox update by settings values in the Student-class itself?
Your actual code clearly won't notify changes to the interface. The reason is simple. Your method that changes the student name is in the Student model and that model does not implement the INotifyPropertyChanged.
There is 2 solutions to fix this issue depending on one question, does the changeStudent() method has to stick with the object model, that is to say, can your requirements allows you to move the changeStudent() method to the view model?
If yes then, first solution, simply remove the changeStudent method from the model and move it to the view model like this:
class StudentViewModel : INotifyPropertyChanged
{
...
public void changeStudent()
{
this.StudentLastName = "McDonald";
}
}
In the other case, second solution, you have to raise events whenever a model property changes and then get your view model to suscribe to these changes. You can proceed like this in the model:
class Student : INotifyPropertyChanged
{
...
private string studentLastName;
public string StudentLastName
{
get
{
return this.studentLastName;
}
set
{
if(this.studentLastname != value)
{
this.studentLastName = value;
this.OnPropertyChanged("StudentLastName");
}
}
}
}
And for the view model:
class StudentViewModel : INotifyPropertyChanged
{
...
public StudentViewModel(Student model)
{
this._model = model;
this._model.PropertyChanged += (sender, e) =>
{
if(e.PropertyName == "StudentLastName")
{
this.OnPropertyChanged("StudentLastName");
}
};
}
}
Both solution will work. It is really import that you understand that your code explicitely needs to notifies the interface whenever a value changes.
ChangeStudent doesn't call any of the methods that trigger a property notify event in the view model, it alters the underlying model instead. It's these events that trigger the view to update itself.
As an aside you should also look at command binding from the view instead of using click handlers in the code-behind. That way your view doesn't need to know anything about the view model that's attached and can be pure presentation.
First you should use commands instead of events.
In your current structure you have to add an
OnPropertyChanged("StudentLastName");
call to your ChangedStudent() Method in StudentViewModel.
After that you have to set the UpdateSourceTrigger of the Bindings to PropertyChanged
Text="{Binding Path=StudentFirstName, UpdateSourceTrigger=PropertyChanged}"

Categories

Resources