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.
Related
I'm working on a MVVM WPF application where i bind several values from ViewModel to View.
Now i have created a new ViewModel where i have to bind a value to a TextBox after a Button click. When i tried this simple binding it didn't work for me. To my surprise the binding works when the value is assigned in the constructor.
I'm confused.
ViewModel:
public ABCViewModel{
txtItems = "Hello world"; //this works
}
private string m_stxtItem = "";
public string txtItems
{
get { return this.m_stxtItem; }
set
{
if (this.m_stxtItem != value)
{
this.m_stxtItem = value;
}
}
}
public ICommand BindTextValue { get { return new RelayCommand(SeriesBinding); } }
private void SeriesBinding()
{
txtItems = "Hi"; //does not work
}
XAML:
<TextBox Text="{Binding txtItems,Source={StaticResource ABCViewModel}}" />
<Button Command="{Binding BindTextValue,Source={StaticResource ABCViewModel}}">Click</Button>
Why this didn't work and where am i wrong?
Simply answer: you are missing the INotifyPropertyChanged-implementation required for automatic data binding.
Extended answer to why it works when setting the value in the constructor:
the initial binding (reading of the value) from the view happens AFTER your ViewModel-constructor was called and your value was set
You need to implement the INotifyPropertyChanged interface on your ViewModel.
Here's some MSDN links on what the interface is and how to implement it:
INotifyPropertyChanged Interface
How to: Implement the INotifyPropertyChanged Interface
Basically because you've set the ViewModel as the DataSource for the View, upon construction, the View will look to the ViewModel for its values. From this point onwards the ViewModel needs some mechanism for notifying the View of changes. The View is not periodically updated or anything like that. This is where the INotifyPropertyChanged interface steps in. The WPF framework looks out for these events being fired and makes the View refresh its value.
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;
Is there a simpeler way to bind many properties?
So if you have a Person class with properties: lastname, firstname, birthday, gender, title, ...
Now I do this for every property on the ViewModel:
public string _LastName;
public string LastName
{
get { return _LastName; }
set { _LastName = value;
RaisePropertyChanged("LastName"); }
}
And on the XAML page this binding:
<TextBlock Text="{Binding FirstName}" />
Now image if Person object has like 20 properties..
So my question is can I do this in a simpeler way?
You only need to raise the PropertyChanged event from the setter of a data-bound property if you actually intend to update the property dynamically at runtime. Otherwise you could use auto-implemented properties without any custom logic:
public FirstName { get; set; }
There is also a NuGet package called Fody that can turn simple public properties into full INotifyPropertyChanged implementations for you automatically: https://github.com/Fody/PropertyChanged
If you use a third party MVVM framework, it also might have a code snippet to create property with INotifyPropertyChanged.
If you use Catel, you can download templates and snippets here:
http://catelproject.com/downloads/general-files/
And here's implementation for Caliburn.Micro:
https://github.com/winterdouglas/propc
The simplest solution is to use the POCO mechanism provided by a free DevExpress MVVM Framework.
POCO will automatically implement INotifyPropertyChanged and raise the PropertyChanged event for all public virtual properties in your view model.
All magic happens when you use the ViewModelSource class to create your view model. You can create your view model in XAML:
<UserControl ...
DataContext="{dxmvvm:ViewModelSource Type=local:MyViewModel}">
Or in code-behind:
this.DataContext = ViewModelSource.Create(() => new MyViewModel());
PREMISE
In a default MVVM scenario, your ViewModel don't have to raise notifications on every property.
Typical case: you get some Person from a database, show them on a View, modify them via TextBoxes and other controls, and click "Save" re-sending them to the database. You can do this by setting the DataContext on the View every time you call the database. This action raises a first update on the bound properties of the control and of every sub-control, so all the getters of the ViewModel's bound properties are called one time and the View get populated with the ViewModel's values. When you modify something on the View, that binding carries the modification to the corresponding ViewModel's property (even a simple plain get-set property).
In this case, you're just fine with something like:
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
//and so on...
}
You need to raise notifications for the ViewModel's properties only if the View must listen to some property's change. For example, this feature: the Button "Save" is enabled if and only if the Name on the Person is not empty. Here, clearly the Button must be able to see when the Name property changes, so that property setter must raise the PropertyChanged event.
A possible implementation:
Use this as base class for ViewModels:
protected abstract BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetAndNotifyIfChanged<T>(
ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
NotifyPropertyChanged(propertyName);
}
}
}
In a derived class you can write every get-set property like this:
class MyViewModel : BaseViewModel
{
public string MyProp
{
get { return _MyProp; }
set { SetAndNotifyIfChanged(ref _MyProp, value); }
}
private string _MyProp;
}
The type T and the parameter propertyName are automatically inferred.
This is the shortest piece of code you could write, and is not so different from a normal full-property:
public string NormalProp
{
get { return _ NormalProp; }
set { _NormalProp = value; }
}
private string _MyProp;
If you don't want to write all this code every time, use a code snippet.
I have a problem with two way binding a TextBox content to a property in another class. Searching stackoverflow gave a lot of tips/solutions but none seem to work.
In my XAML code I have:
< TextBox ... Width="336" IsReadOnly="True"
Text="{Binding Path=AssignedClearProgram, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
(... I removed all non important items)
In the accompanies cs code I have:
public CombiWindow(Combi combi)
{
ViewModel = new CombiViewModel(combi);
DataContext = ViewModel;
}
In the CombiViewModel:
[UsedImplicitly]
public string AssignedClearProgram { get; set; }
It seems that the first time I assign AssignedClearProgram, the textbox is filled with the text that I set, however after the window is displayed and AssignedClearProgram gets updated from the code (i.e. the set method is called), the data is not updated in the screen.
Does anybody have a solution to update the textbox when this variable is changed?
Kind regards,
Michel
Your viewmodel class needs to implement INotifyPropertyChanged and you need to raise that interface's event whenever you change the property. Then the binding will spot changes and update the textbox.
Your view model class should implement the INotifyPropertyChanged interface.
Your property would then look like the following:
private string assignedClearProgram;
public string AssignedClearProgram
{
get { return assignedClearProgram; }
set
{
if (assignedClearProgram != value)
{
assignedClearProgram = value;
// Notify property has changed here using PropertyChanged event from INotifyPropertyChanged.
}
}
}
Read this article for an example of how to implement the INotifyPropertyChanged interface and utilize its PropertyChanged event.
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}" />