This question already has answers here:
How to Enable/Disable button in wpf
(5 answers)
Closed 3 years ago.
I have a button which is:
<Button Style="{StaticResource ButtonStylePermit1}"
Click="permit1_Click" x:Name="permit1" IsEnabled="False"/>
and in my .cs code I also have a int floor = 1;
I want to be able to enable the button if the floor = 2 and disable the button if not. What is the best possible way to do this?
in your .cs code, create a method that either disables or enables the button, then call that method whenever floor changes value. So a simple way to achieve that is to create a Public Property on floor member, and use the property to change the value of floor as well as to change the enabled property of you button.
private int floor;
public int Floor{
get { return this.floor;}
set {
this.floor = value;
if (this.floor == 2)
{ this.permit1.Enabled = false }
else {this.permit1.Enabled = true}
}
}
this.Floor = 1;
Two solution are provided as follows:
Straight Forward idea:
if (floor == 2) permit1.IsEnabled = true;
else permit1.IsEnabled = false;
Ternary
permit1.IsEnabled = (floor==2) ? true : false;
You can try the following XAML code as an example:
<WrapPanel>
<Button x:Name="permit1" Content="Test" Width="200" Click="permit1_Click" IsEnabled="False" Background="Red"/>
<Button x:Name="Button1" Content="Press here" Width="100" Click="Button_Click"/>
</WrapPanel>
And the MainWindow.xaml.cs includes the following C# code:
int floor;
Random random01 = new Random();
public MainWindow()
{
InitializeComponent();
}
private void permit1_Click(object sender, RoutedEventArgs e)
{
//your code here
}
private void Button_Click(object sender, RoutedEventArgs e)
{
floor = (int)Math.Round(random01.NextDouble(), 0) + 1;
Button1.Content = floor;
permit1.IsEnabled = (floor==2) ? true : false;
/*
if (floor == 2)
permit1.IsEnabled = true;
else
permit1.IsEnabled = false;
*/
}
In WPF typical way for handling this would be to use data binding with value convertor
Here is a quick look at the changes you will need to do:
In your xaml you will bind your control's IsEnabled property to Floor
<Button Content="Click Me!" Click="button_Click"
x:Name="permit1" IsEnabled="{Binding Floor, Converter={StaticResource converter}}">
Floor is an int, while IsEnabled is the bool, to link these you need the converter, I have put in the converter as a windows resources so that it can be used for multiple different objects (in the same class, but you can put it in a central resource.xaml file).
<Window.Resources>
<local:FloorToEnableConverter x:Key="converter" />
</Window.Resources>
For the framework to be able to find your property Floor it should be in the datacontext of the XAML, I have done this in the code behind constructor
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
You will need to define the logic for this converter in your own class, which is just the implementation of IValueConverter interface
public class FloorToEnableConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
throw new ArgumentException("value cannot be null");
if ((int)value == 1)
return false;
if ((int)value == 2)
return true;
// for all other values disable
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Finally when the property changes you will need to notify the UI about the changes, so this needs to be implemented by INotifyPropertyChangeInterface
I have implemented this on my demo class
public partial class MainWindow : Window, INotifyPropertyChanged
And the actual code is:
public event PropertyChangedEventHandler PropertyChanged;
public int floor;
public int Floor
{
get { return this.floor;}
set { this.floor = value;
// The string here should exactly match the property name
OnPropertyChanged("Floor"); }
}
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
This should work, also even though in this example I have implemented the code for change notification in the code behind class, in WPF try to avoid using the code behind class, that was a required in win forms, but WPF provides alternate mechanism for this. Have a look at the MVVM pattern for a better way to structure the code.
Related
Im looking for a solution in WPF to change the IsEnabled property of a button based on the content of a textbox. The TextBox holds a numeric value. If the value is greater than a certain value the IsEnabled property of the button should be set to true, as long as it is below this value the property should be false.
I have been looking around but couldn't find a proper solution. What i found here on CodeProject is almost what im looking for. But the problem is that this approach just checks if any content is in the textbox. But i need to check/compare the numeric content.
I would prefer to find a way to do it in XAML. Alternatively i could implement it also in my ViewModel. But i dont have an idea how to do it! I was thinking about to notify the button via my INotifyChanged event from the property that is shown in the textbox. But i couldnt find out how.
Followed some code. But, sorry, there is nothing beside the textbox and the button since i couldnt find a way to solve that.
<TextBox Name ="tbCounter" Text ="{Binding CalcViewModel.Counter, Mode=OneWay}" Background="LightGray" BorderBrush="Black" BorderThickness="1"
Height="25" Width="50"
commonWPF:CTextBoxMaskBehavior.Mask="Integer"
commonWPF:CTextBoxMaskBehavior.MinimumValue="0"
commonWPF:CTextBoxMaskBehavior.MaximumValue="1000"
IsReadOnly="True"/>
<Button Name="btnResetCount" Focusable="True" Content="Reset" Command="{Binding Path=CalcViewModel.ResetCounter}" Style="{StaticResource myBtnStyle}"
Width="100" Height="25">
Is there a common way to set the IsEnabled property of a control based on a property/value in the XAML or in the ViewModel?
EDIT This is my ViewModel, i extracted the related members and properties only otherwise the post would be too long:
class CalcViewModel:INotifyPropertyChanged
{
private CCalc _calc;
public int Counter
{
get
{ return _calc.Counter; }
set{ _calc.Counter = value;}
}
public event PropertyChangedEventHandler PropertyChanged;
void ResetCounterExecute()
{ _calc.Counter = 0; }
bool CanResetCounterExecute()
{
if (_calc.Counter > 0)
{ return true; }
else
{ return false; }
}
public ICommand ResetCounter
{ get { return new RelayCommand(ResetCounterExecute, CanResetCounterExecute); } }
public CCalcViewModel()
{
this._calc = new CCalcViewModel();
this._calc.PropertyChanged += new PropertyChangedEventHandler(OnCalcPropertyChanged);
}
private void OnCalcPropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.RaisePropertyChanged(e.PropertyName);
}
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You want to combine an element property binding:
IsEnabled={Binding ElementName=Textbox, Path=Text}
With a valueconverter
IsEnabled={Binding ElementName=Textbox, Path=Text, Converter={StaticResource IsAtLeastValueConverter}}
IsAtLeastValueConverter.cs
namespace WpfPlayground
{
public class IsAtLeastValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (System.Convert.ToInt32(value) > 5)
{
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
}
Oh I forgot you'll need to add this to your control:
<Window.Resources>
<wpfPlayground:IsAtLeastValueConverter x:Key="IsAtLeastValueConverter" />
</Window.Resources>
Edit: VM Version
I've put in elipsis (...) where I didn't make changes to your code.
<Button ... IsEnabled={Binding Path=ButtonIsEnabled} ...>
class CalcViewModel:INotifyPropertyChanged
{
private CCalc _calc;
private bool _buttonIsEnabled;
public ButtonIsEnabled {
get { return _buttonIsEnabled; }
set {
_buttonIsEnabled = value;
RaisePropertyChanged("ButtonIsEnabled");
}
}
public int Counter
{
get
{ return _calc.Counter; }
set{
_calc.Counter = value;
_buttonIsEnabled = _calc.Counter > 5;
}
}
...
}
So what happens here is when you change the counter value, you set the ButtonIsEnabled property which raises the property changed event and updates the button on the form with whatever logic you're using to determine if the button should be enabled.
Edit: You might need to remove that Binding=OneWay from the textbox, I'm not sure if it will initiate the set property if you're using that setting.
If you wish to do it directly in the XAML (I wouldn't necessarily recommend this, as validation should probably be done in the view model), you can also use a package, such as https://quickconverter.codeplex.com/ - this allows you to write some C# (ish) in a binding.
I've used it before, and it can make it pretty easy, eg you install the package, add the line to the very start of your application:
QuickConverter.EquationTokenizer.AddNamespace(typeof(object));
which adds the System namespace to QuickConverter (the line above works as object is in the System namespace), and then you can simply do:
IsEnabled="{qc:Binding 'Int32.TryParse($P) && Int32.Parse($P) >= 3', P={Binding ElementName=tbCounter, Path=Text}}"
If & breaks your Intellisense, you can instead write:
IsEnabled="{qc:Binding 'Int32.TryParse($P) ## Int32.Parse($P) >= 3', P={Binding ElementName=tbCounter, Path=Text}}"
(Where 3 is the value you're testing against).
EDIT:
Sorry, on re-reading you XAML, it can be written even more straightforwardly as follows:
IsEnabled="{qc:Binding '$P >= 3', P={Binding CalcViewModel.Counter}}"
You should change your ViewModel to something like this
public class ViewModel:INotifyPropertyChanged
{
public int Counter
{
get { return _counter; }
set {
_counter = value;
RaisePropChanged("Counter");
//for example
if (value>3)
{
IsButtonCounterEnabled = true;
}
else
{
IsButtonCounterEnabled = false;
}
}
}
public bool IsButtonCounterEnabled
{
get { return _IsButtonCounterEnabled; }
set { _IsButtonCounterEnabled = value;
RaisePropChanged("IsButtonCounterEnabled");
}
}
private void RaisePropChanged(string propName)
{
PropertyChanged(this,new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate{};
private int _counter;
private bool _IsButtonCounterEnabled;
}
and then Bind your button like this
<Button IsEnabled="{Binding IsButtonCounterEnabled,Mode=OneWay}" Content="Button" HorizontalAlignment="Left" Height="47" Margin="215,57,0,0" VerticalAlignment="Top" Width="159"/>
Hope this help
So here I am again, asking a very similar question to yesterday. I re-factored my project in order to better follow the MVVM pattern. Now my binding is no longer working as it was yesterday. I am trying to bind the visibility of a dock panel to a button. Here is some of my code:
ViewModel:
public class SelectWaferButtonViewModel : INotifyPropertyChanged
{
private bool isClicked;
public SelectWaferButtonViewModel()
{
isClicked = false;
}
public bool IsControlVisible
{
get
{
return isClicked;
}
set
{
isClicked = value;
OnPropertyChanged("IsControlVisible");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnButtonClick()
{
if (isClicked)
{
IsControlVisible = false;
}
else
{
IsControlVisible = true;
}
}
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
XAML:
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisConverter"/>
<local:SelectWaferButtonViewModel x:Key="SelectWaferButton" />
<local:WaferTrackerWindowViewModel x:Key="WindowViewModel" />
</Window.Resources>
<DockPanel
Name="tvwDockPanel"
DataContext="{StaticResource SelectWaferButton}"
Width="225"
Visibility="{Binding IsControlVisible, Mode=TwoWay,
FallbackValue=Collapsed,
Converter={StaticResource BoolToVisConverter}}"
DockPanel.Dock="Left">
</DockPanel>
My BoolToVisConverter:
public class BoolToVisibilityConverter : IValueConverter
{
public BoolToVisibilityConverter() { }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool bValue = (bool) value;
if (bValue)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
Visibility visibility = (Visibility) value;
if (visibility == Visibility.Visible)
{
return true;
}
else
{
return false;
}
}
}
I apologize for a question that is similar to yesterday, but I am struggling with this MVVM stuff since I am quite new to WPF. Any help will be much appreciated.
Thanks in advanced,
EDIT:
Here is some extra code snippets for further reference:
public class WaferTrackerWindowViewModel :INotifyPropertyChanged
{
private SelectWaferButtonViewModel btnSelectWaferViewModel;
public event PropertyChangedEventHandler PropertyChanged;
private DelegateCommand exitCommand;
private DelegateCommand expandPanelCommand;
private DelegateCommand selectWaferCommand;
public WaferTrackerWindowViewModel()
{
this.InstantiateObjects();
initThread.RunWorkerAsync();
}
public string SelectedWafer
{
get
{
return selectedWafer;
}
set
{
selectedWafer = value;
}
}
public ICommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand = new DelegateCommand(Exit);
}
return exitCommand;
}
}
public ICommand ExpandPanelCommand
{
get
{
if (expandPanelCommand == null)
{
expandPanelCommand = new DelegateCommand(ExpandPanel);
}
return expandPanelCommand;
}
}
public ICommand SelectWaferCommand
{
get
{
if (selectWaferCommand == null)
{
selectWaferCommand = new DelegateCommand(SelectWafer);
}
return selectWaferCommand;
}
}
private void InstantiateObjects()
{
btnSelectWaferViewModel = new SelectWaferButtonViewModel();
initThread = new BackgroundWorker();
}
private void ExpandPanel()
{
btnSelectWaferViewModel.OnButtonClick();
}
private void SelectWafer()
{
//Does Nothing Yet
}
private void Exit()
{
Application.Current.Shutdown();
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void InitThread_DoWork(object sender, DoWorkEventArgs e)
{
TreeViewPresenter tvwPresenter = new TreeViewPresenter();
tvwPresenter.WaferList = DataLibrary.GetWaferList();
}
private void InitThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
tvwPresenter.TreeView.DataContext = tvwPresenter.ProcessesAndWafers;
tvwPresenter.WaferListCache = tvwPresenter.ProcessesAndWafers;
tvwPresenter.ProcessArray = tvwPresenter.WaferListCache.ToArray();
}
}
When the "expand panel" button gets clicked, it calls the ExpandPanel command, which routes the execution to the method "private void ExpandPanel()" in this same class. Then, in the ExpandPanel() method, it calls the OnButtonClick() method on the btnSelectWaferViewModel object, which will change the IsControlVisible property. This change should then be reflected onto the bound dock panel, but this is not happening
Kyle
(1) ViewModel should be in the Window.DataContext section, not the Window.Resources section.
(2) In your view model, make your IsControlVisible property a System.Windows.Visibility, rather than a Boolean, then you don't need a converter.
(3) I don't see any way for OnButtonClick to fire, and it really needs to be set up with ICommand interface.
(4) You don't need to implement ConvertBack because the Visibility property you're binding to is one way by definition. There is no way for the user to set the visibility to false.
(5) Don't mix accessing IsClicked and it's accessor IsControlVisible. Always use the Accessor in MVVM, because you run the risk of accidentally setting IsClicked which won't activate OnPropertyChanged.
All in all, you're pretty close. Make sure to keep an eye on your "Output" window, it will tell you if a binding is failing for some reason. But yeah, hang in there!
So when you do this:
<Window.Resources>
<local:SelectWaferButtonViewModel x:Key="SelectWaferButton" />
</Window.Resources>
WPF will create a new instance of the SelectWaferButtonViewModel and add it to it's resources. You then bind to this by setting the DataContext using the StaticResource with the key.
However, if you are then creating another SelectWaferButtonViewModel in your code behind and linking up your command to that instance, then it's not the same instance, so changes to the properties of this unbound instance won't effect your UI. There are a couple of ways around it. You can either a) create a single SelectWaferButtonViewModel in the code behind as a property and then bind to that in XAML, or b) Declare your SelectWaferButtonViewModel in XAML as you currently have it and then retrieve that instance in your code behind, like this:
SelectWaferButtonViewModel swbvm = (SelectWaferButtonViewModel)this.FindResource("SelectWaferButton");
Edit: So after seeing your last edit, if you want to go with a) then I would suggest you expose btnSelectWaferViewModel as a property in your WaferTrackerWindowViewModel and then bind to that property with the DataContext of your Window set to the WaferTrackerWindowViewModel instance. So you end up with something like:
<DockPanel
Name="tvwDockPanel"
Width="225"
Visibility="{Binding MyButton.IsControlVisible,
Converter={StaticResource BoolToVisConverter}}"
DockPanel.Dock="Left">
</DockPanel>
and:
public class WaferTrackerWindowViewModel :INotifyPropertyChanged
{
private SelectWaferButtonViewModel btnSelectWaferViewModel;
public SelectWaferButtonViewModel MyButton
{
get { return btnSelectWaferViewModel; }
set
{
btnSelectWaferViewModel = value;
OnPropertyChanged("MyButton");
}
}
//......
I've been playing about with data binding and using the INotifiedProperty interface (including the new .Net 4.5 CallerMemberName attribute).
All is working well but I can't understand why updating an object's property refreshes the label it's bound to but refreshing the object itself doesn't re-fresh the label.
For example, if I have the following Window:
<Grid Name="TestGrid">
<!-- Grid definitions here -->
<Label Grid.Column="0" Grid.Row="0">The value is :</Label>
<Label Grid.Column="1" Grid.Row="0" Content="{Binding TestVal1}"/>
<Button Grid.Column="0" Grid.Row="1" Click="Button_Click_1">Refresh</Button>
<Button Grid.Column="1" Grid.Row="1" Click="Button_Click_2">New class instance</Button>
</Grid>
With the following code behind it:
public MainWindow()
{
InitializeComponent();
TestGrid.DataContext = TestClass1;
}
public TestClass TestClass1 = new TestClass();
private void Button_Click_1(object sender, RoutedEventArgs e)
{
TestClass1.ChangeTestVal1();
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
TestClass1 = new TestClass();
}
Which is bound the following class:
public class TestClass : INotifyPropertyChanged
{
public TestClass()
{
ChangeTestVal1();
}
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged([CallerMemberName] String caller = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(caller));
}
}
private string _TestVal1;
public string TestVal1
{
get { return _TestVal1; }
set
{
if (value != _TestVal1)
{
_TestVal1 = value;
OnPropertyChanged();
}
}
}
public void ChangeTestVal1()
{
TestVal1 = "TestVal1 = " + DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss");
}
The result of is that clicking the "Refresh" button works and clicking the "New class instance" doesn't.
My question is, I know I can add "TestGrid.DataContext = TestClass1" to the code for the second button to get it to work but surely it should detect the instance of the TestClass changing when it's refreshed? Am I setting the binding up incorrectly?
You're assigning TestGrid.DataContext = TestClass1; Changing the object reference of the variable to a different object does not change the object reference in the DataContext property of the TestGrid. Take a look at the basics of OOP for more details
Edit: I mean, doing TestClass1 = new TestClass(); does not change the fact that the Datacontext of your grid is still the same object instance it was before.
That's happening because the data context has the reference to the TestClass1. What you are doing is assign a new reference in the TestClass1 that is not bound to your DataContext.
The actual update for the binding is triggered via:
OnPropertyChanged();
which is in turn triggered when you set the property, not when you set the whole class. So setting the class has not visible effect in the label, as expected.
I'm trying to understand how I can call a Resfresh() ou Work() method each time I modify an option on a window with WPF (XAML). I already ask the question but I wasn't clear enough. So I will ask again with a better example.
I would like to know how I can update a label from many visual component. Let say we have 10 checkbox with label 0 to 9 and I would like to do the sum of them if they are checked.
In classic Winform I'll create an event handler OnClick() and call the event on each CheckBox state change. OnClick call a Refresh() global method. Refresh evaluate if each CheckBox is checked and sum them if required. At the end of the Refresh() method I set the Label Text property to my sum.
How can I do that with XAML and data binding ?
<CheckBox Content="0" Name="checkBox0" ... IsChecked="{Binding Number0}" />
<CheckBox Content="1" Name="checkBox1" ... IsChecked="{Binding Number1}" />
<CheckBox Content="2" Name="checkBox2" ... IsChecked="{Binding Number2}" />
<CheckBox Content="3" Name="checkBox3" ... IsChecked="{Binding Number3}" />
<CheckBox Content="4" Name="checkBox4" ... IsChecked="{Binding Number4}" />
...
<Label Name="label1" ... Content="{Binding Sum}"/>
In my ViewModel I have a data binded property for each checkBox and one for the Sum
private bool number0;
public bool Number0
{
get { return number0; }
set
{
number0 = value;
NotifyPropertyChanged("Number0");
// Should I notify something else here or call a refresh method?
// I would like to create something like a global NotifyPropertyChanged("Number")
// But how can I handle "Number" ???
}
}
// Same for numer 1 to 9 ...
private bool sum;
public bool Sum
{
get { return sum; }
set
{
sum = value;
NotifyPropertyChanged("Sum");
}
}
private void Refresh() // or Work()
{
int result = 0;
if (Number0)
result = result + 0; // Could be more complex that just addition
if (Number1)
result = result + 1; // Could be more complex that just addition
// Same until 9 ...
Sum = result.ToString();
}
My question is how and when should I call this Refresh method?
You got several stuff wrong in your design. Here is what I would do:
Instead of having Number0...9, make a BindingList<bool> Numbers
Then in XAML, show the checkboxes like this:
<ItemsControl ItemsSource="{Binding Numbers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding} Content="Name the checkbox here" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Label Content="{Binding Numbers, Converter={StaticResource NumbersToSumConverter}}" />
With NumbersToSumConverter being an IValueConverter, such as:
public NumbersToSumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var numbers = value as BindingList<bool>();
//Do your sum here.
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
That is an example of how it's done in MVVM, if you need to store more than just a bool in the BindingList<T>,
create a new class that implements INotifyPropertyChanged, and add as many properties as you need (make sure to raise PropertyChanged in their setter).
Then use that as the type of your BindingList<T>.
Hope this helps,
Bab.
try something like -
private bool number0;
public bool Number0
{
get { return number0; }
set
{
number0 = value;
NotifyPropertyChanged("Number0");
NotifyPropertyChanged("Sum");
}
}
public bool Sum
{
get { return this.EvaluateSum(); }
}
private bool EvaluateSum()
{
int result = 0;
if (Number0)
result = result + 0; // Could be more complex that just addition
if (Number1)
result = result + 1; // Could be more complex that just addition
// Same until 9 ...
return result.ToString();
}
Note: this is not tested.
If you don't like the above then you can do the following change:
Declare new class
public class SomeClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool number0;
public bool Number0
{
get { return number0; }
set
{
number0 = value;
this.NotifyPropertyChanged("Number0");
}
}
private bool sum;
public bool Sum
{
get { return sum; }
set
{
sum = value;
this.NotifyPropertyChanged("Sum");
}
}
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null))
{
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
Note: Change the Binding correspondingly
On Property change of SomeClass:
void SomeClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.Contains("Number"))
{
(sender as SomeClass).Sum = EvaluateSum(); // put or call the sum logic
}
}
Personally I would just call Refresh() in each Number setter like you suggest.
I'm not keen on directly raising PropertyChanged notifications within setters for other properties. What happens in the future if some other property changes as a result of one of the Number* properties changing? You'd have to go through every setter and raise PropertyChanged events for that property too. It goes against the single reponsibility principle. Number* setters are there to set the property, not worry about the results of other properties changing.
With a dedicated Refresh() function, that function takes care of updating other properties, and they raise their own change events as normal.
Yes, calling Refresh() in a setter is not fantastic, but the "proper" way to do it would be for your viewmodel to subscribe to its own PropertyChanged event and call Refresh() in the event handler. This adds unnecessary complication IMO, and calling Refresh() in each setter is effectively the same logic.
say I have this control:
public partial class bloc999 : UserControl
{
bloc999Data mainBlock = new bloc999Data();
public bloc999()
{
InitializeComponent();
mainBlock.txtContents = "100";
base.DataContext = mainBlock;
}
}
in the xaml:
<TextBox Margin="74,116,106,0" Name="txtContents"
Text="{Binding Path=txtContents, UpdateSourceTrigger=PropertyChanged,Mode = TwoWay}" />
<TextBox Margin="74,145,106,132" Name="txtContents2"
Text="{Binding Path=txtContents2, UpdateSourceTrigger=PropertyChanged,Mode = TwoWay}" />
Then I have this class:
public class bloc999Data : INotifyPropertyChanged
{
string _txtContents;
string _txtContents2;
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
public string txtContents2
{
get
{
return this._txtContents2;
}
set
{
if (int.Parse(value) > int.Parse(this._txtContents))
{
this._txtContents2 = "000";
}
else
this._txtContents2 = value;
NotifyPropertyChanged("txtContents2");
}
}
public string txtContents
{
get
{
return this._txtContents;
}
set
{
this._txtContents = value;
NotifyPropertyChanged("txtContents");
}
}
}
Ok now say I have A button on the form and I do this in the code:
mainBlock.txtContents2 = "7777777";
It puts 000 in the textbox, but If i just type in manually, in the textbox (txtContents2), the setter code is called but for some reason the textboxes value does not change, the instance value does change. help?
I believe it's just because the value is changing within the context of the data binding operation, so WPF just ignores it because it knows the value is changing and thinks the event is superfluous. What it doesn't know is that you've gone and changed the value from the value WPF has to something else again.
If you do the notification in a separate message then WPF will process it outside the context of the current data binding operation and will thus pick up the change:
if (int.Parse(value) > int.Parse(this._txtContents))
{
this._txtContents2 = "000";
// notify WPF of our change to the property in a separate message
Dispatcher.BeginInvoke((ThreadStart)delegate
{
NotifyPropertyChanged("txtContents2");
});
}
else
{
this._txtContents2 = value;
NotifyPropertyChanged("txtContents2");
}
This assumes your view model has access to the Dispatcher. An example of how to do so is shown in my blog post on a base ViewModel class.
I was having similar problem earlier here
In your usercontrol, update Binding and set UpdateSourceTrigger to Explicit
<TextBox Margin="74,145,106,132" x:Name="txtContents2" TextChanged="txtContents2_TextChanged"
Text="{Binding Path=txtContents2, UpdateSourceTrigger=Explicit,Mode = TwoWay}" />
then in the TextChanged event handler update the binding manually by validating the input.
move validation logic from property txtContent2's setter in bloc999Data in this event handler
private void txtContents2_TextChanged(object sender, TextChangedEventArgs e)
{
if (int.Parse(txtContents2.Text) > int.Parse(mainBlock.txtContents))
{
mainBlock.txtContents2 = "000";
txtContents2.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
}
else
{
mainBlock.txtContents2 = txtContents2.Text;
txtContents2.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
and it works.
Hope it helps!!