How to bind ValueChanged of DecimalUpDown Control on ViewModel? - wpf

<xctk:DecimalUpDown Value="{Binding TotalAmount, Mode=TwoWay}"
HorizontalAlignment="Right"
Grid.Column="1"
VerticalAlignment="Center"
Width="120" Height="21" Margin="5 0 0 0" />

In MVVM, you would put the ValueChanged logic in the setter of the TotalAmount source property as this property gets set then when value is changed, e.g.:
public decimal TotalAmount
{
get { return _totalAmout; }
set { _totalAmount = value; PropertyBoundToDecimalUpDown3 = _totalAmount + _otherAmount; }
}
There is no reason to handle the event itself when you bind to a source property.

You must write your logic inside the callback of each property. The ValueChanged event handler approach requires tightly coupled code behind and it will break the MVVM pattern.
If you prefer the DP way:
public decimal Value1
{
get { return (decimal)GetValue(Value1Property); }
set { SetValue(Value1Property, value); }
}
public static readonly DependencyProperty Value1Property =
DependencyProperty.Register("Value1", typeof(decimal), typeof(MyVM),
new PropertyMetadata(0m, (d, e) =>
{
var vm = d as MyVM;
vm.TotalValue = e.NewValue + vm.Value2;
}));
public decimal Value2
{
get { return (decimal)GetValue(Value2Property); }
set { SetValue(Value2Property, value); }
}
public static readonly DependencyProperty Value2Property =
DependencyProperty.Register("Value2", typeof(decimal), typeof(MyVM),
new PropertyMetadata(0m, (d, e) =>
{
var vm = d as MyVM;
vm.TotalValue = e.NewValue + vm.Value1;
}));
Or if you prefer the INotifyPropertyChanged way:
public decimal Value1
{
get => _value1;
set {
_value1 = value;
TotalValue = value + Value2;
NotifyPropertyChanged();
}
}
public decimal Value2
{
get => _value2;
set
{
_value2 = value;
TotalValue = value + Value1;
NotifyPropertyChanged();
}
}
Note that writing TotalValue = ... will handle its own NotifyPropertyChanged. However writing _totalValue = ... does not.

Related

How to change datacontext at runtime with Mvvm

I have a graph that I want to change some ViewModel property so the whole graph would change accordingly.
The only property that I want to change here is "year", I tried to implement the INotifyPropertyChanged so the binding will cause the graph to change automatically, but it didn't work.
This is the model:
public class Model
{
public double rate { get; set; }
public string date { get; set; }
}
This is the ViewModel:
public class ViewModel :INotifyPropertyChanged
{
private string _year;
public string Year { get { return _year; } set { _year = value;UpdateData(); OnPropertyChanged("Year"); } }
public ViewModel()
{
_year = "2017";
UpdateData();
}
public void UpdateData()
{
int i,j;//Indexs that holds actuall api retrived values
string cR, urlContents;// cR- current rate in string format, urlContents - the whole Api retrived data
string c;//For api syntx, add 0 or not, depends on the current date syntax
this.CurrenciesHis = new ObservableCollection<Model>();//Model objects collection
HttpClient client = new HttpClient();
for (int l = 1; l < 13; l++)
{
if (l < 10)
c = "0";
else
c = "";
urlContents = client.GetStringAsync("http://data.fixer.io/api/"+(_year)+"-"+ c + l + "-01?access_key=&base=USD&symbols=EUR&format=1").Result;
i = urlContents.IndexOf("EUR");//Finds the desired value from api recived data
j = urlContents.IndexOf("}");
cR = urlContents.Substring(i + 5, (j - 2) - (i + 5));
CurrenciesHis.Add(new Model() { rate = Convert.ToDouble(cR), date = "01/" + l.ToString() });
}
}
public ObservableCollection<Model> CurrenciesHis { get; set; }
#region "INotifyPropertyChanged members"
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
This is the View that based on third party control (I deleted alot of XAML and used bold letters to mark where is the actuall binding located):
<layout:SampleLayoutWindow x:Class="AreaChart.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ResizeMode="CanResizeWithGrip"
xmlns:chart="clr-namespace:Syncfusion.UI.Xaml.Charts;assembly=Syncfusion.SfChart.WPF"
xmlns:local="clr-namespace:PL"
xmlns:layout="clr-namespace:Syncfusion.Windows.SampleLayout;assembly=Syncfusion.Chart.Wpf.SampleLayout"
UserOptionsVisibility="Collapsed"
WindowStartupLocation="CenterScreen" Height="643.287" Width="1250.5"
Title="2017">
<Grid>
<Grid.Resources>
...........................................
<chart:AreaSeries x:Name="AreaSeries" EnableAnimation="True"
**XBindingPath="date"
Label="Favourite"
YBindingPath="rate"
ItemsSource="{Binding CurrenciesHis}"**
ShowTooltip="True" >
<chart:AreaSeries.AdornmentsInfo>
<chart:ChartAdornmentInfo AdornmentsPosition="Bottom"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ShowLabel="True">
<chart:ChartAdornmentInfo.LabelTemplate>
<DataTemplate>
....................................
<TextBox HorizontalAlignment="Left" Height="30" Margin="28,231,0,0" TextWrapping="Wrap" Name="Text1" VerticalAlignment="Top" Width="76" Text="{Binding Year, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
This is the code behaind and the event of the Textbox that I want to change with it's help that year property of the viewmodel:
public partial class MainWindow : SampleLayoutWindow
{
PL.ViewModel newInstance;
public MainWindow()
{
InitializeComponent();
newInstance = new PL.ViewModel();
this.DataContext = newInstance;
}
}
What I understand is that from this point the mechanism of WPFwill change the values on the chart using the binding and the "notification" of INotifyPropertyChanged but it doesn't work for me..
year should be a private field, but it is public. You're setting the value of the field, which naturally does not execute any of the code in the setter for the property.
Make year and all of your backing fields private, and rename all of your private fields with a leading underscore (for example, year should be renamed to _year) to prevent accidents like this.
And make it a policy in your viewmodel code always to set the property, never the field, except of course inside the actual property setter for that field.
Also, use bindings to set viewmodel properties from UI. Don't do it in codebehind. Get rid of that textchanged handler.
<TextBox
HorizontalAlignment="Left"
Height="30"
Margin="28,231,0,0"
TextWrapping="Wrap"
VerticalAlignment="Top"
Width="76"
Text="{Binding Year, UpdateSourceTrigger=PropertyChanged}"
/>
Finally, it seems that you intended for changes to Year to have some effect on the contents of CurrenciesHis, but there's no mechanism for that in your code, and no explanation of what you want to have happen or how you expect it to happen.
And here's an updated version of your viewmodel.
public class ViewModel
{
public ViewModel()
{
// DO NOT, DO NOT EVER, DO NOT, SERIOUSLY, EVER, EVER, EVER UPDATE A
// PROPERTY'S BACKING FIELD OUTSIDE THE PROPERTY'S SETTER.
Year = DateTime.Now.Year - 1;
UpdateCurrencies();
}
protected void UpdateCurrencies()
{
// Indexs that holds actuall api retrived values
int i, j;
// cR- current rate in string format, urlContents - the whole Api retrived data
string cR, urlContents;
// For api syntx, add 0 or not, depends on the current date syntax
string c;
CurrenciesHis = new ObservableCollection<Model>();//Model objects collection
HttpClient client = new HttpClient();
for (int l = 1; l < 13; l++)
{
if (l < 10)
c = "0";
else
c = "";
// Use the public property Year, not the field _year
var url = "http://data.fixer.io/api/" + Year + "-" + c + l + "-01?access_key=&base=USD&symbols=EUR&format=1";
urlContents = client.GetStringAsync(url).Result;
i = urlContents.IndexOf("EUR");//Finds the desired value from api recived data
j = urlContents.IndexOf("}");
cR = urlContents.Substring(i + 5, (j - 2) - (i + 5));
CurrenciesHis.Add(new Model() { rate = Convert.ToDouble(cR), date = "01/" + l.ToString() });
}
OnPropertyChanged(nameof(CurrenciesHis));
}
// Year is an integer, so make it an integer. The binding will work fine,
// and it'll prevent the user from typing "lol".
private int _year;
public int Year
{
get { return _year; }
set
{
if (_year != value)
{
_year = value;
OnPropertyChanged(nameof(Year));
UpdateCurrencies();
}
}
}
public ObservableCollection<Model> CurrenciesHis { get; private set; }
// -----------------------------------------------------
// YearsList property for ComboBox
// 30 years, starting 30 years ago.
// You could make this IEnumerable<int> or ReadOnlyCollection<int> if you
// want something other than the ComboBox to use it. The ComboBox doesn't care.
// Year MUST be an int for the binding to SelectedItem (see below) to work,
// not a string.
public System.Collections.IEnumerable YearsList
=> Enumerable.Range(DateTime.Now.Year - 30, 30).ToList().AsReadOnly();
}
XAML for YearsList combobox (which I prefer to the text box btw):
<ComboBox
ItemsSource="{Binding YearsList}"
SelectedItem="{Binding Year}"
/>
Your CurrenciesHis property doesn't implement INPC so WPF doesn't realize that you changed it (UpdateData() has "this.CurrenciesHis = new ObservableCollection();")
Your current property is:
public ObservableCollection<Model> CurrenciesHis { get; set; }
Should be something like this:
private ObservableCollection<Model> _CurrenciesHis;
public ObservableCollection<Model> CurrenciesHis { get { return _CurrenciesHis; } set { if (_CurrenciesHis != value) { _CurrenciesHis = value; OnPropertyChanged("CurrenciesHis"); } } }

WPF Binding in columns not working

I have a list ob object like this:
public class Device : ObjectBase
{
private int _DeviceNbr;
public int DeviceNbr
{
get { return _DeviceNbr; }
set { _DeviceNbr = value; }
}
private string _DeviceName;
public string DeviceName
{
get { return _DeviceName; }
set { _DeviceName = value; OnPropertyChanged(); }
}
private ObservableCollection<State> _DeviceStates;
public ObservableCollection<State> DeviceStates
{
get { return _DeviceStates; }
set { _DeviceStates = value; OnPropertyChanged(); }
}
}
public class State: ObjectBase
{
public int StateNbr { get; set; }
private string _stateType;
public string StateType
{
get { return _stateType; }
set { _stateType = value; }
}
private int _value;
public int Value
{
get { return _value; }
set { _value = value; OnPropertyChanged(); }
}
}
which I need to bind to a Datagrid.
My approach is to create a customDataGrid which looks like this:
public class CustomGrid : DataGrid
{
public ObservableCollection<ColumnConfig> ColumnConfigs
{
get { return GetValue(ColumnConfigsProperty) as ObservableCollection<ColumnConfig>; }
set { SetValue(ColumnConfigsProperty, value); }
}
public static readonly DependencyProperty ColumnConfigsProperty =
DependencyProperty.Register("ColumnConfigs", typeof(ObservableCollection<ColumnConfig>), typeof(CustomGrid), new PropertyMetadata(new PropertyChangedCallback(OnColumnsChanged)));
static void OnColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dataGrid = d as CustomGrid;
dataGrid.Columns.Clear();
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "Nbr", Binding = new Binding("DeviceNbr") });
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "Device", Binding = new Binding("DeviceName") });
foreach (var columnConfig in dataGrid.ColumnConfigs.Where(c => c.IsVisible))
{
var column = new DataGridTextColumn()
{
Header = columnConfig.ColumnHeader,
Binding = new Binding("DeviceStates")
{
ConverterParameter = columnConfig.ColumnName,
Converter = new DeviceStateConverter(),
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Mode =BindingMode.TwoWay
}
};
dataGrid.Columns.Add(column);
}
}
}
public class DeviceStateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ObservableCollection<State> DeviceStates && parameter != null)
{
var DeviceState = DeviceStates.FirstOrDefault(s => s.StateType == parameter.ToString());
if (DeviceState != null)
return DeviceState.Value;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The ViewModel looks like this:
public class MainViewModel : ObjectBase
{
private ObservableCollection<Device> _Devices;
public ObservableCollection<Device> Devices
{
get { return _Devices; }
set { _Devices = value; OnPropertyChanged(); }
}
private ObservableCollection<ColumnConfig> _columnConfigs;
public ObservableCollection<ColumnConfig> ColumnConfigs
{
get { return _columnConfigs; }
set { _columnConfigs = value; OnPropertyChanged(); }
}
public MainViewModel()
{
Devices = new ObservableCollection<Device>();
_columnConfigs = new ObservableCollection<ColumnConfig>()
{
new ColumnConfig(){ ColumnHeader = "On", ColumnName = "On", ColumnWidth= 100, IsVisible= true},
new ColumnConfig(){ ColumnHeader = "Off", ColumnName = "Off", ColumnWidth= 100, IsVisible= true}
};
for ( int i = 0; i <= 100; i++)
{
_Devices.Add(new Device()
{
DeviceNbr = i,
DeviceName = "Device " + i.ToString(),
DeviceStates = new ObservableCollection<State>()
{
new State() { StateType = "On", Value= i},
new State() { StateType = "Off", Value= i+1}
}
});
}
OnPropertyChanged("ColumnConfigs");
OnPropertyChanged("Devices");
}
public void TestStateChange ()
{
Devices[2].DeviceName = "Device X";
Devices[2].DeviceStates[0].Value = 5;
// OnPropertyChanged("Devices");
}
}
And the XAML like this:
<local:CustomGrid
AutoGenerateColumns="False"
ColumnConfigs="{Binding ColumnConfigs}"
ItemsSource="{Binding Devices, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
Here is the result:
Application
Now the problem is that the binding for the Devicesstates does not work.
In the ViewModel is a method called "TestStateChange" where tried to change that state. The state in the DeviceStatesCollection changed like expected but it doesn't reflect to the view. Can someone please provide me some help?
UPDATE
The binding works but PropertyChanged Event when changing the value of a state does not fire.
public void TestStateChange()
{
foreach (var device in Devices)
{
foreach (var state in device.DeviceStates)
{
state.Value = state.Value + 1;
}
device.OnPropertyChanged("DeviceStates");
}
}
So I have to raise the PropertyChangedEvent on the "parent" collection of the state. That's weird.
The only think that I can now think of, is to implement an event in the State class and let the parent collection object subscribe to it.
Does someone has a better idea?
If your State object implements INotifyPropertyChanged (which all your bindable data objects should), and if your datagrid column binds directly to a State object (i.e. via a binding path that contains an index) then your UI will update as you expect.
You could subscribe to the State property change events from the parent Device object and then re-notify from there, but that is a long winded way to do it and means that you have to subscribe to the CollectionChanged event of the ObservableCollection that contains the State objects so that you can attach/unattach from the PropertyChange event on each State object (sometimes this will be the only way, but avoid it if you can).

How to let a property change event fire when its sub-property changes?

I have a class AdvanceBasicEffect, it has a property SpecularColor which is object of class AdvanceVector3 class, so when i bind specularColor.X property, property change event fires but only in AdvanceVector3 class not in AdvanceBasicEffect.
See the code you will figure out :
public partial class Lights : UserControl
{
public Lights()
{
InitializeComponent();
this.DataContext = this;
basicEffect = new AdvanceBasicEffect();
}
public AdvanceBasicEffect basicEffect { get; set; }
}
public class AdvanceBasicEffect : INotifyPropertyChanged
{
public AdvanceBasicEffect()
{
SpecularColor = new AdvanceVector3();
basicEffect = ((bathroom)CurrentWindowHandle.currentGame.Components.First()).basicEffect;
}
BasicEffect basicEffect;
AdvanceVector3 _SpecularColor;
public AdvanceVector3 SpecularColor
{
get
{
return _SpecularColor;
}
set
{
//Line 1 : event not occuring
_SpecularColor = value;
if(basicEffect!=null)
basicEffect.DirectionalLight0.Direction = new Vector3(_SpecularColor.X, _SpecularColor.Y, _SpecularColor.Z);
valueChanged("SpecularColor");
}
}
private void valueChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_80", basicEffect, false);
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_111", basicEffect, false);
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_112", basicEffect, false);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class AdvanceVector3 : INotifyPropertyChanged
{
float _X;
public float X
{
get
{
return _X;
}
set
{
_X = value;
valueChanged("X");
}
}
float _Y;
public float Y
{
get
{
return _Y;
}
set
{
_Y = value;
valueChanged("Y");
}
}
float _Z;
public float Z
{
get
{
return _Z;
}
set
{
_Z = value;
valueChanged("Z");
}
}
private void valueChanged(string p)
{
//line 2 :Event Occuring
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
//CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_80", basicEffect, false);
//CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_111", basicEffect, false);
//CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_112", basicEffect, false);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
At comment "Line 1" event is occurring whereas on "Line 2" event occur, so my question is when the sub property changes then why is the parent property change not occuring. I can not define X,Y,Z in same class as i have many properties which are going to need AdvanceVector3 class
Binding code is as follows :
<Slider ToolTip="SpecularColorX" Minimum="-1" Maximum="1" Value="{Binding basicEffect.SpecularColor.X}" />
<Slider ToolTip="SpecularColorY" Minimum="-1" Maximum="1" Value="{Binding basicEffect.SpecularColor.Y}" />
<Slider ToolTip="SpecularColorZ" Minimum="-1" Maximum="1" Value="{Binding basicEffect.SpecularColor.Z}" />
If I follow your question correctly, I think your misunderstanding what INPC means and when it triggers.
If you say have
Class ParentClass : INotifyPropertyChanged {
public ChildClass SomeObject {
get { ... }
set { ... valueChanged("SomeObject"); }
}
...
}
and
Class ChildClass : INotifyPropertyChanged {
public string SomeString {
get { ... }
set { ... valueChanged("SomeString"); }
}
...
}
Now if you either from xaml or code-behind change SomeString in an object of ParentClass
var parentObject = new ParentClass {SomeObject = new ChildClass {SomeString = "Hi"}};
// will trigger property changed in ChildClass for SomeString property
// however it will not trigger a PropertyChanged in ParentClass for SomeObject
parentObject.SomeObject.SomeString = "New String"
// will trigger property changed in ParentClass for SomeObject property
parentObject.SomeObject = new ChildClass();
Your xaml bindings work fine since you bind directly to the properties and as and when they change your View is updated accordingly.
If you are wanting to "observe" any changes to SomeString from inside the ParentClass, then you need to subscribe to the PropertyChanged event of ChildClass.SomeString
so say ParentClass can be updated to
public ChildClass SomeObject {
get { ... }
set {
if (value == _someObject)
return;
_someObject.PropertyChanged -= ChildObjectValueChanged;
_someObject = value;
_someObject.PropertyChanged += ChildObjectValueChanged;
valueChanged("SomeObject"); }
}
private void ChildObjectValueChanged(object sender, PropertyChangedEventArgs args) {
if (args.PropertyName == "SomeString")
// Child Object property "SomeString" has changed. Do what you need here
}
Your setter never gets called. Wpf itself calls PropertyChagned on INotifyPropertyChanged imlplementations. What you should do is to subsribe to changed event yourself in your AdvancedBasicEffect class constructor instead of setting mesh effect in valueChanged method:
public AdvanceBasicEffect()
{
SpecularColor = new AdvanceVector3();
basicEffect = ((bathroom)CurrentWindowHandle.currentGame.Components.First()).basicEffect;
SpecularColor.PropertyChanged += (o,e) =>
{
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_80", basicEffect, false);
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_111", basicEffect, false);
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_112", basicEffect, false);
}
}

RelayCommand Params & Binding

View:
Playing with a basic calculator using WPF(MVVM).
I've 1 TextBox for the first num, 1 TextBox for the second num, 1 TextBlock for the results and 1 Button to execute the AddCommand and return the result.
What's the right XAML syntax to bind these controls to the right Data.
Model:
public class Operation : INotifyPropertyChanged
{
private double _result;
public Operation()
{
_result = 0;
}
public double Result
{
get { return _result; }
set
{
if (value != _result)
{
_result = value;
RaisePropertyChanged("Result");
}
}
}
public double DoAdd(double first, double second)
{
_result = first + second;
return _result;
}
}
ViewModel:
public class CalcViewModel
{
private Operation _operation;
public RelayCommand AddCommand { get; set; }
public CalcViewModel()
{
_operation = new Operation();
// This is not correct, how to define the AddCommand here so it takes two params
// The first and second nums to work with.
AddCommand = new RelayCommand(first, second => ExecuteAddCommand(first, second));
}
private void ExecuteAddCommand(double first, double second)
{
// How to bind this returned double to the TextBlock in View
_oepration.DoAdd(first, second);
}
}
EDIT new version of code on request of Vlad
Model:
public class Operation
{
private double _result;
public Operation()
{
_result = 0;
}
public double Result
{
get { return _result; }
}
public void PerformAdd(double leftNum, double rightNum)
{
_result = leftNum + rightNum;
}
}
ViewModel:
public class CalcViewModel
{
private Operation _operation;
public double LeftNumber { get; set; }
public double RightNumber { get; set; }
public double Result { get; set; }
public RelayCommand AddCommand { get; set; }
public CalcViewModel()
{
AddCommand = new RelayCommand(a => ExecuteAddCommand());
_operation = new Operation();
}
private void ExecuteAddCommand()
{
_operation.PerformAdd(LeftNumber, RightNumber);
Result = _operation.Result;
}
View XAML:
<TextBox Text="{Binding LeftNumber}" />
<TextBox Text="{Binding RightNumber}" />
<TextBox Text="{Binding Result}" />
<Button Content="Add" Command="{Binding AddCommand}" />
View Code behind:
public partial class CalcUserControl : UserControl
{
CalcViewModel vm;
public CalcUserControl()
{
InitializeComponent();
vm = new CalcViewModel();
this.DataContext = vm;
}
}
I tried all modes of binding without any result. I have here an additional question, what's the default binding mode in such a situation?
I even thought that it has to do with the datatype of the calculation, so I swiched from double to int, but still not working.
Well, let's see what can be done.
1) Model. The model doesn't need anything fancy. I would keep it simple and make it just return the value and not use NotifyPropertyChanged. After all, it's a model.
public class BinaryOperation
{
double _l, _r, _result = 0.0;
public Operation(double l, double r)
{
_l = l; _r = r;
}
public double Result
{
get { return _result; }
}
public PerformAdd()
{
_result = _l + _r;
}
}
2) ViewModel. Here, your RelayCommand doesn't really need any arguments. But you need to store the values of operands in your VM, so that your view can bind to them, instead of sending them in a command. Remember, business logic doesn't belong to view, view just blindly binds to the VM! So you need 3 DPs (left addend, right addend, result) in your VM.
3) When the command arrives, you just take the addends from VM, ask the model to perform the operation, retrieve the result and assign it to your VM's result DP. (Right now, your model operations are fast, so you don't need to do it in asynchronous way. But maybe in the future...)
4) View. You need for your Window/UserControl just to bind to the VM's properties.
Its going to be something as simple as:
<TextBox Text="{Binding LeftAddend}"/>
<TextBox Text="{Binding RightAddend}"/>
<TextBox Text="{Binding Result}"/>
<Button Command="{Binding AddCommand}">Add</Button>
(Don't forget to set the DataContext right.)
Edit:
the VM class has to be a dependency object! And the properties should b defined as dependency properties. Something like this:
public class CalcViewModel : DependencyObject
{
private Operation _operation;
public double LeftNumber
{
get { return (double)GetValue(LeftNumberProperty); }
set { SetValue(LeftNumberProperty, value); }
}
public static readonly DependencyProperty LeftNumberProperty =
DependencyProperty.Register("LeftNumber", typeof(double), typeof(CalcViewModel));
public double RightNumber
{
get { return (double)GetValue(RightNumberProperty); }
set { SetValue(RightNumberProperty, value); }
}
public static readonly DependencyProperty RightNumberProperty =
DependencyProperty.Register("RightNumber", typeof(double), typeof(CalcViewModel));
public double Result
{
get { return (double)GetValue(ResultProperty); }
set { SetValue(ResultProperty, value); }
}
public static readonly DependencyProperty ResultProperty =
DependencyProperty.Register("Result", typeof(double), typeof(CalcViewModel));
public RelayCommand AddCommand { get; set; }
public CalcViewModel()
{
AddCommand = new RelayCommand(a => ExecuteAddCommand());
_operation = new Operation();
}
private void ExecuteAddCommand()
{
_operation.PerformAdd(LeftNumber, RightNumber);
Result = _operation.Result;
}
}
Or, if you want to do it with INotifyPropertyChanged, and you are working with .NET 4.5
public class CalcViewModel : INotifyPropertyChanged
{
private Operation _operation;
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
double _leftNumber;
public double LeftNumber
{
get { return _leftNumber; }
set
{
if (value == _leftNumber) return;
_leftNumber = value;
NotifyPropertyChanged();
}
}
double _rightNumber;
public double RightNumber
{
get { return _rightNumber; }
set
{
if (value == _rightNumber) return;
_rightNumber = value;
NotifyPropertyChanged();
}
}
double _result;
public double Result
{
get { return _result; }
set
{
if (value == _result) return;
_result = value;
NotifyPropertyChanged();
}
}
public RelayCommand AddCommand { get; set; }
public CalcViewModel()
{
AddCommand = new RelayCommand(a => ExecuteAddCommand());
_operation = new Operation();
}
private void ExecuteAddCommand()
{
_operation.PerformAdd(LeftNumber, RightNumber);
Result = _operation.Result;
}
}
The same with older .NET versions:
void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
double _leftNumber;
public double LeftNumber
{
get { return _leftNumber; }
set
{
if (value == _leftNumber) return;
_leftNumber = value;
NotifyPropertyChanged("LeftNumber");
}
}
etc.
Thank you all and especially #Vlad. Just one tiny fault, y've declared the property Result twice on class CalcViewModel : DependencyObject.
It works now fine :)

MVVM property with a linked complex class and CanExecute Relay Command not working

I have a entity class in a C# library class and linked to Silverlight class library
(entities must be in C# class because of legacy compatiblity with other systems)
Example (C# library):
public class TestClass
{
private string _testValue;
public string TestValue
{
get { return _testValue; }
set
{
if (_testValue!= value)
{
_testValue = value;
OnPropertyChanged("TestValue");
}
}
}}
This class is linked to Silverlight class library.
On a MVVM there is a property
private TestClass _testProp = new TestClass();
public TestClass TestProp
{
get
{
return _testProp ;
}
set
{
if (value != _testProp )
{
_testProp = value;
RaisePropertyChanged("TestProp");
PressCommand.CanExecuteChanged();
}
}
}
The property is binded to a control in XAML
<TextBox Text="{Binding TestProp.TestValue, Mode=TwoWay}">
<Button Content="Press" Command="{Binding PressCommand}" />
I want to control the button with RelayCommands CanExecute depended on the TestValue in TestClass...
PressCommand = new RelayCommand(() =>
{
DoSomething();
}, () => TestProp.TestValue != string.empty);
However, if the TestValue in changed (different then empty string), PressCommand CanExecute doen't seem to notice the change and is not enabled, making it unusable...
Is it possible to use the CanExecute with this kind of set-tu
What you need to do is call PressCommand.CanExecuteChanged() when the value changes. To do this nicely listen for the property change of the value in the property
VM
public TestClass TestProp
{
get
{
return _testProp ;
}
set
{
if (value != _testProp )
{
if(_testProp != null)
{
_testProp.PropertyChanged -= TestPropChanged;
}
_testProp = value;
if(_testProp != null)
{
_testProp.PropertyChanged += TestPropChanged;
}
RaisePropertyChanged("TestProp");
PressCommand.CanExecuteChanged();
}
}
}
private void TestPropChanged(object sender, PropertyChangedEventArgs e)
{
//Can check for specific property if required...
PressCommand.CanExecuteChanged();
}

Resources