INotifyPropertyChanged binding not updating as expected - c#

So here is the problem I'm beating my head against: I have a custom user control that exposes two dependency properties that are bound to my ViewModel. In my ViewModel I have an instance of a class that holds multiple properties that express values that relate to the user control as well as to items that control manipulates. Here's a bit of sample code to explain it visually so here is a simple sample of my control, it's a Slider that is combined with a checkbox that allows the user to lock the slider.
<custom:SliderControl IsLocked="{Binding Path=CustomClass.IsLocked, Mode=TwoWay}" SliderValue="{Binding Path=CustomClass.Value, Mode=TwoWay}" />
IsLocked and SliderValue are dependency properties that effectively manipulate the checkbox and slider that are contained in the custom control. All of the control functions work as intended, except for the bindings to the class I've defined. If I create individual properties, as in one int property and one bool property the bindings work as intended. However I have five sliders, and each slider in my actual code has five properties that tie in to them. I'm trying to eliminate code duplication by creating a class to hold these properties in a reusable object shrinking my 25 properties down to 5 class instances.
My CustomClass inherits ObservableObject and has a bool property and int property named IsLocked and SliderValue respectively. For more visual aids here is what it looks like:
public class CustomClass : ObservableObject
{
public const string SliderValuePropertyName = "SliderValue";
private int _sliderValue= 0;
public int SliderValue
{
get
{
return _sliderValue;
}
set
{
if (_sliderValue== value)
{
return;
}
_sliderValue= value;
RaisePropertyChanged(SliderValuePropertyName );
}
}
public const string IsCheckedPropertyName = "IsChecked";
private bool _isChecked = false;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
if (_isChecked == value)
{
return;
}
_isChecked = value;
RaisePropertyChanged(IsCheckedPropertyName);
}
}
The ViewModel property is very similar and looks like this, an new instance of the class is created when the ViewModel loads:
public const string SliderOnePropertyName = "SliderOne";
private CustomClass _sliderOne;
public CustomClass SliderOne
{
get
{
return _sliderOne;
}
set
{
if (_sliderOne== value)
{
return;
}
_sliderOne= value;
RaisePropertyChanged(SliderOnePropertyName );
}
}
Why won't the updating of the dependency property that is bound to the property in the class update properly? Is it because you can't properly update the class instance property by itself and instead have to update the entire class instance whenever changes occur? Or do I need to further customize the setter in this ViewModel property? As it sits now changing the slider value or checkbox never hits the bound property at all and nothing errors out when debugging.
EDIT: I've also surrounded the control in a Border and set the Border UIElement's DataContext to that of the class and then subsequently applied the more simple path binding to the underlying custom control. This however did not have any effect on my problem.
I'm a homegrown programmer so I often miss things when putting code together and I'm guessing this is the case here, unless what I'm trying just won't work.
Any help would be greatly appreciated.
EDIT: So I've been toying around with using a custom event that will let me know when the specific property of the custom control changes and then having that event wired up in my ViewModel to update the existing class. This works but still creates code duplication as now I have to have 10 events, 2 events per control, one to check for when the value of the slider changes and the other to detect when the checkbox IsChecked value changes. This code duplication exists since you can't route multiple command parameters (like a simple string identifier for which slider is being manipulated as well as the value you want to use in the code). This limitation means I can't just use 2 events that differentiate between which control is undergoing changes within the defined method as exposing the physical control to the ViewModel breaks the MVVM pattern. Using a class as the datacontext for the user control made it so I didn't care what control was being manipulated as they each had their own class instance. Using events this unravels the MVVM pattern as now I need to know which of the five controls is being manipulated by the user.
It can't be this hard to use a class in property bindings. I have to be missing something remedial.

here is a full example:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
public SliderValues slv { get; private set; }
public ViewModel()
{
slv = new SliderValues();
}
}
public class SliderValues : INotifyPropertyChanged
{
bool _isLocked = false;
public bool IsLocked
{
get { return _isLocked; }
set
{
_isLocked = value;
OnPropertyChanged("IsLocked");
}
}
int _theValue = 5;
public int TheValue
{
get { return _theValue; }
set
{
_theValue = value;
OnPropertyChanged("TheValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Now the xaml:
<UserControl x:Class="TestBindings.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Slider Height="23" HorizontalAlignment="Left" Margin="114,138,0,0" Name="slider1" VerticalAlignment="Top" Width="100"
DataContext="{Binding slv}" Value="{Binding TheValue, Mode=TwoWay}"/>
</Grid>
</UserControl>

May be there is just a syntactical error. Try this
{Binding Path=CustomClass.IsLocked, Mode=TwoWay}

Try this...<custom:SliderControl DataContext="{Binding CustomClass}" IsLocked="{Binding IsLocked, Mode=TwoWay}" SliderValue="{Binding Value, Mode=TwoWay}" />

Related

Label Content binding only updating once

My label only seems to get the data from the property it is bound to once. I have the Property raising the Property Changed event in the setter, but when the value of the property gets changed, it raises the event properly (I know this because of the break point I set), but the text in the Label on the window doesn't change. I should maybe also note that the window with the label isn't the main window, but a new one that pops up.
ViewModel:
public class PurchaseVerificationViewModel : ViewModelBase
{
private WindowService.WindowService windowService = new WindowService.WindowService();
private string _verificationQuestion = "Question"; //default so i can check if it changed in the window
public string VerificationQuestion
{
get { return _verificationQuestion; }
set
{
if (_verificationQuestion != value)
{
_verificationQuestion = value;
OnPropertyChanged(nameof(VerificationQuestion));
}
}
}
}
Window:
<Window>
<Window.DataContext>
<viewmodels:PurchaseVerificationViewModel/>
</Window.DataContext>
<Grid>
<Label Content="{Binding VerificationQuestion, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
There's no problem with your implementation of the INotifyPropertyChanged, since you are correctly invoking the notification that your property has been modified.
So if the problem is not with the one who's raising the notification, might it rather be with what is actively listening to it?
And the problem is that you're defining the DataContext of your Window to the class itself, rather than to the instance which you are utilizing and modifying in the code-behind of your application.
What is actually happening under the hoods, due to the way you defined your DataContext in xaml, is that a new PurchaseVerificationViewModel class is being constructed (is the not the one who are modifying on your logic) and therefore your VerificationQuestion will return it's default value (or rather the private backing field default value, "Question").
In reality the problem is that you have induced your listener to listen to the wrong thing.
Since you want the content of the Label (target) to be update based on a source change, what you have to do, is to set as the DataContextof the Window the specific instance which you are modifying on the logic of your application, and make sure you define it as a property!
public PurchaseVerificationViewModel myViewModel {get;set;}
For instance after InitializeComponent(), on your page constructor, you could initialize the property and set it as the DataContext, like this:
myViewModel = new PurchaseVerificationViewModel();
this.DataContext = myViewModel;

WPF binding to a global variable to update UI

Let's say I have two windows and a trayicon context menu. Each of the windows has a togglebutton and the context menu has a checkable menu item. All three controls are designed to display and toggle the status of the same value.
How can I bind, in this case IsChecked, of the three controls to a single global variable that when one of the controls is checked/unchecked that the other controls will update accordingly? Should I just do an invoke or is there an MVVM solution? I'm new to WPF so I'm not sure the best/most correct way to accomplish this.
Lets say you have WindowA, WindowB, ..., WindowN and assume that they all are of different type.
Create a class, lets say CommonState, that encapsulates all common properties, commands, etc. and implements INotifyPropertyChanged
public class CommonState : INotifyPropertyChanged
{
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
if (value != _isChecked)
{
_isChecked = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then declare an interface:
public interface ICommonStateWindow
{
CommonState { get; set; }
}
Make each window implement this interface:
public partial class WindowA : Window, ICommonState
{
public WindowA()
{
InitializeComponent();
}
// This property will be injected, do not re-assign
public CommonState CommonState { get; set; }
}
Inject the common state in each window prior to showing it, for example:
public partial class App : Application
{
private CommonState _state;
protected override void OnStartup(StartupEventArgs e)
{
_state = new CommonState() {IsChecked = true};
var wndA = new WindowA() { CommonState = _state };
var wndB = new WindowB() { CommonState = _state };
wndA.Show();
wndB.Show();
}
}
Remember to keep at least one reference to the created CommonState in some long living object (like App or the main window), so it does not get garbage collected at some point.
In the XAML you should bind using a RelativeSource, so that each new type of window you create can have its own independent ViewModel (DataContext):
<Window x:Class="Example.WindowA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowA" Height="300" Width="300">
<Grid>
<CheckBox IsChecked="{Binding CommonState.IsChecked, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</Grid>
</Window>
The example, I've demonstrated is not the only way and I won't say "the best", but it solves the following problems:
Encapsulates the common (shared) state
Synchronizes the state between different instances (or types) of windows
Allows the CommonState to be extended independently of the window implementation (only the XAML needs to be updated)
Another possible solution is to register a singleton instance of the CommonState into a statically exposed inversion of control container (IoC) and make each concrete window's ViewModel obtain an instance to it. In this way you will avoid the injection step. This would be an overkill for small projects
I anyone is trying to run the above code, remember to remove StartupUri="MainWindow.xaml" from App.xaml
You can add to your codebehind bool IsChecked property and use it for all component you want. And you can change it components' event method to true or false.

UWP - Change button.Visibility from another class

I am trying to change multiple buttons visibility from another class. This is supposed to be easy, but I just don't understand it.
The xaml part is straight forward:
<button x:Name="whatever" Visibility="{Binding whateverName}"
The view-model could be something like this?
private Visibility vis;
public Visibility Vis
{
get { return vis; }
set { vis = value; }
}
But if that is the case, how do I pass my button name?
To go a bit further, a services file is trying to modify the visibility value..
Thanks in advance.
Since you're using Bindings, you don't need the button name identifier.
The connection is made in the Binding part of the XAML:
<Button x:Name="whatever" Visibility="{Binding whateverName}"/>
What is happening there is that you are saying the Visibility property of the whatever button will be bound to the whateverName property value in your view model.
So your View model needs to look like this:
private Visibility vis;
public Visibility whateverName
{
get { return vis; }
set { vis = value; }
}
To change the visibility of your button you need to change the value of whateverName in your view model.
However, if you try, you'll notice that that won't work. The reason is that in order for the change to take effect on the button, the View model must notify the view that its property has changed. This is done with the INotifyPropertyChanged interface.
So your view model will need to look something like this:
public class Viewmodel : INotifyPropertyChanged
{
private Visibility vis;
public Visibility whateverName
{
get { return vis; }
set
{
vis = value;
OnPropertyChanged("whateverName");
}
}
public void OnPropertyChanged(string pName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
In the PropertyChanged event you must pass the property name that you want to notify. In my example I just used a string value that matches the property name but there are various techniques to eliminate that "magic string".
Here's one SO question that has good answers.

wpf c# data binding to set string using property of viewModel object

I am trying to solve this issue for so many hours:
I have user custom control of grid named NewMazeGrid and I want to use it as a control in MainWindow. MainWindow contains MazeViewModel(mazeVM member).
I'm trying to set the values of the grid, when the property MazeViewModel:MySingleplay changes.
(I'm using the INotifyPropertyChanged for it, and it works perfectly fine. I guess, the problem is in the final binding)
The code:
This is the property MazeViewModel:MySingleplay getter:
public string MySingleplay
{
get
{
if (myModel.MySingleplay == null)
{
return "";
} else
{
return myModel.MySingleplay.ToString();//works perfect
}
}
}
this is the NewMazeGrid.xaml.cs:
namespace VisualClient.View.controls
{
public partial class NewMazeGrid : UserControl
{
private MazePresentation myMaze;
private string order; //dont really use it
//Register Dependency Property
public static readonly DependencyProperty orderDependency =
DependencyProperty.Register("Order", typeof(string), typeof(NewMazeGrid));
public NewMazeGrid()
{
myMaze = new MazePresentation();
InitializeComponent();
DataContext = this;
lst.ItemsSource = myMaze.MazePuzzleLists;
}
public string Order
{
get
{
return (string)GetValue(orderDependency);
}
set
{
SetValue(orderDependency, value);
myMaze.setPresentation(value); //(parsing string into matrix)
}
}
}
}
this is the MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private MazeViewModel mazeVM;
public MainWindow()
{
InitializeComponent();
mazeVM = new MazeViewModel(new ClientMazeModel(new TCPClientConnection()));
DataContext = mazeVM;
mazeVM.connectToServer();
}
private void bu_Click(object sender, RoutedEventArgs e)
{
bool isC = mazeVM.isConnected();
mazeVM.openSingleplayGame("NewMaze");//works perfect
}
this is the MainWindow.xaml:
<Window x:Class="VisualClient.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Controls ="clr-namespace:VisualClient.View.controls"
xmlns:vm ="clr-namespace:VisualClient.ViewModel"
xmlns:local="clr-namespace:VisualClient.View"
mc:Ignorable="d"
Title="Main Window" Height="350" Width="525" MinWidth="900" MinHeight="600">
<WrapPanel >
<Button Name ="bu" Content="Click_Me" Click="bu_Click"/>
<Grid Name="myGrid">
<Controls:NewMazeGrid Order="{Binding MySingleplay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</WrapPanel>
</Window>
I get this error on the binding line: Value cannot be null.
To sum:
It initialize fine the window in the ctor, but when the property changes it does not get into the Order property setter. therefor my grid never changes.
What should be the right syntax for binding in this case? how do I bind it to the right property?
Folders hierarchy explorer
WPF may not call the CLR wrapper of a dependency property, but just directly call the GetValue and SetValue methods of the underlying DependencyObject. This is why there should not be any logic except the GetValue and SetValue calls.
This is explained in XAML Loading and Dependency Properties:
Because the current WPF implementation of the XAML processor behavior
for property setting bypasses the wrappers entirely, you should not
put any additional logic into the set definitions of the wrapper for
your custom dependency property. If you put such logic in the set
definition, then the logic will not be executed when the property is
set in XAML rather than in code.
Similarly, other aspects of the XAML processor that obtain property
values from XAML processing also use GetValue rather than using the
wrapper. Therefore, you should also avoid any additional
implementation in the get definition beyond the GetValue call.
To get notified about property value changes, you can register a PropertyChangedCallback by property metadata. Note also that there is a naming convention for DependencyProperty fields. Yours should be called OrderProperty:
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register(
"Order", typeof(string), typeof(NewMazeGrid),
new PropertyMetadata(OnOrderChanged));
public string Order
{
get { return (string)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
private static void OnOrderChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((NewMazeGrid)obj).myMaze.setPresentation((string)e.NewValue);
}
Besides that, you must not set
DataContext = this;
in the constructor of NewMazeGrid. This effectively prevents inheriting the DataContext from the parent window, so that {Binding MySingleplay} won't work. Except under special circumstances you should never explicitly set a UserControl's DataContext.
So, remove the DataContext assignment from the constructor:
public NewMazeGrid()
{
myMaze = new MazePresentation();
InitializeComponent();
lst.ItemsSource = myMaze.MazePuzzleLists;
}
That said, there is also no need to set UpdateSourceTrigger=PropertyChanged on a one-way binding. It only has an effect in two-way (or one-way-to-source) bindings:
<Controls:NewMazeGrid Order="{Binding MySingleplay}"/>

WPF Custom Controls

I've been trying for ever to try and figure this out.
Story: I have one MainWindow and 2 User Controls.
When the MainWindow loads One control is visible and the other is not.
Once the user enters their data and settings, I need to make the other form visible.
The form that is invisible at startup needs to be initialized, because it is gathering data from the WMI of the computer it is running on. It is also gathering AD Information in preparation for the user.
For some reason I cannot get one form to show the other.
I think this is what I'm supposed to be looking at:
#region Class Variable
public string ShowSideBar { get { return (String)GetValue(VisibilityProperty); } set { SetValue(VisibilityProperty, value); }}
public DependencyProperty VisibilityProperty = DependencyProperty.Register("ShowSideBar", typeof(string), typeof(UserControl), null);
#endregion
This is set in my MainWindow Class, however, I have no idea why I cannot call it from any other usercontrol.
Is there any way to just expose something like this to all my forms from my MainWindow?
public int RowSpan {
get { return Grid.GetRowSpan(DockPanel1); }
set { Grid.SetRowSpan(DockPanel1,value); }
}
Dependency properties must be static. Why is the type string? Should it not be Visibility if you wish to bind the visibility of the controls to it?
Does it have to be a dependency property? You could just use a regular property as well and implement INotifyPropertyChanged, since you are not binding this field to anything, rather binding other things to it.
For a dependency property, try something like this instead:
public static readonly DependencyProperty SideBarVisibilityProperty = DependencyProperty.Register("SideBarVisibility", typeof(Visibility), typeof(MyTemplatedControl), null);
public Visibility SideBarVisibility
{
get { return (Visibility)GetValue(SideBarVisibilityProperty); }
set { SetValue(SideBarVisibilityProperty, value); }
}
Firstly, this application would benefit from application of the MVVM pattern.
However, without taking that approach, you can still resolve the problem you have. It would be unusual for a user control to rely on knowing what its parent is. The code behind for your main window would be the better place to put this code. (Not as good as a view model... but that's another story.)
Add to the control that should cause the side bar to be made visible an event, ShowSideBar. Attach a handler in the main window, and use the handler to display the second control. No need for dependency properties here at all.
public class MyControl : UserControl
{
...
public event EventHandler ShowSideBar;
// Call this method when you need to show the side bar.
public void OnShowSideBar()
{
var s = this.ShowSideBar;
if (s != null)
{
s(this, EventArgs.Empty);
}
}
}
public class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.FirstControl.ShowSideBar += (s, e) =>
{
this.SecondControl.Visibility = Visibility.Visible;
}
}
}
I fixed the initlized Component but changing.
X:Class="AdminTools.MainWindow.ShowSideBar" to x:Class="AdminTools.MainWindow".
now i have an issues where
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:User="clr-namespace:AdminTools.Controls.User"
xmlns:Custom="clr-namespace:AdminTools.Controls.Custom"
xmlns:Bindings="clr-namespace:AdminTools.Functions"
x:Class="AdminTools.MainWindow"
Title="MainWindow" Height="691.899" Width="1500"
>
<Window.DataContext>
<Bindings:ShowSideBar />
</Window.DataContext>
<Bindings:ShowSideBar /> = ShowSideBar does not exist in the namespace clr-namespace:AdminTools.Functions
ShowSideBar: member names cannot be the same as their enclosing type.

Categories