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");
}
}
//......
Related
I have a ViewModel that is a DependencyObject for which the DependencyPropertys are not updating the View with the new values.
A sample property (the get/set wrapper is called as expected)
public static readonly DependencyProperty WeaponNameProperty = DependencyProperty.Register(
"WeaponName",
typeof(string),
typeof(WeaponSystemVM),
new PropertyMetadata(null, new PropertyChangedCallback(OnWeaponNameChanged)));
public string WeaponName
{
get { return (string)GetValue(WeaponNameProperty); }
set { SetValue(WeaponNameProperty, value); }
}
The Callback (called when WeaponName is changed)
private static void OnWeaponNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WeaponSystemVM vm = d as WeaponSystemVM;
if (vm != null)
{ vm.CommandAddWeaponSystem.RaiseCanExecuteChanged(); }
}
The CanExecute Delegate (gets run as expected and updates the relevant Button)
private bool CanAddSystem()
{
if (string.IsNullOrWhiteSpace(WeaponName)) return false;
if (string.IsNullOrWhiteSpace(WeaponLock)) return false;
if (string.IsNullOrWhiteSpace(WeaponDamage)) return false;
if (string.IsNullOrWhiteSpace(WeaponAttack)) return false;
return true;
}
The input TextBox
<TextBox x:Name="NameInput" Text="{Binding WeaponName, Mode=TwoWay}" Margin="12,4" RelativePanel.Below="NameAdorner" RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"/>
The output TextBlock (is NOT updated with the new value and the DataContext is the same as the input TextBox)
<TextBlock Text="{Binding WeaponName}"/>
Frustratingly, it seems to be just this implementation that isn't working.
In an attempt to reproduce the issue, I created a seperate project without all the extra info associated with my app, and the View is being updated exactly as expected.
What I don't understand is what is not being done correctly in this implementation. The ViewModel is updating exactly as expected. The Bindings are valid according to the LiveVisualTree.
Can anyone point me to the issue?
You shouldn't use DependencyPropertys in your ViewModel: it is a markup class, used for binding on the View side. Overkill and out of scope for it being used that way.
You should implement INotifyPropertyChanged, and fire the INotifyPropertyChanged.PropertyChanged event in every single property you want to notify the UI about.
Something like:
your ViewModel inherits from
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetAndRaiseIfChanged<T>(
ref T backingField,
T newValue,
[CallerMemberName] string propertyName = null)
{
if (!object.Equals(backingField, newValue))
return;
backingField = newValue;
this.RaisePropertyChanged(propertyName);
}
}
and in your ViewModel you define your property like
private string _weaponName;
public string WeaponName
{
get { return this._weaponName; }
set { SetAndRaiseIfChanged(ref this._weaponName, value); }
}
a more concise CanAddSystem
private bool CanAddSystem()
{
return
!string.IsNullOrWhiteSpace(WeaponName)
&& !string.IsNullOrWhiteSpace(WeaponLock)
&& !string.IsNullOrWhiteSpace(WeaponDamage)
&& !string.IsNullOrWhiteSpace(WeaponAttack);
}
build your ViewModel's command with something that implements ICommand interface (something like a RelayCommand)
the View piece would be
<TextBlock Text="{Binding WeaponName}"/>
and you're done: when you bind an ICommand to the UI, the system automatically updates the CanExecute reading it from the ViewModel.
I am new to the binding concept and got stuck with the following.
public sealed partial class MainPage : Page
{
Model model;
public MainPage()
{
this.InitializeComponent();
model = new Model();
this.DataContext = model;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
model.Name = "My New Name";
}
}
class Model : DependencyObject
{
public static DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Model), new PropertyMetadata("My Name"));
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
I have bound the Name property to Text property of TextView. All I need to do is, on the button click I want to update the Name value that will have to update the text box value. I thought, if I use dependency property instead of normal CLR property, I dont need to implement INotifyPropertyChanged.
But the value in the UI is not updating as expected. Am I missing something?
Thanks in advance.
There are a couple things that need to be addressed with your question. First of all, your model does not need to inherit from DependencyObject, rather it should implement INotifyPropertyChanged:
public class Model : INotifyPropertyChanged
{
string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
An object that implements INotifyProperty can then be used as a DependencyProperty in your page/window/object:
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model",
typeof(Model), typeof(MainWindow));
public Model Model
{
get { return (Model)GetValue(ModelProperty); }
set { SetValue(ModelProperty, value); }
}
Finally, then, you can bind your TextBox.Text property to that in the XAML:
<Grid>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Name}"/>
<Button Click="Button_Click">Click</Button>
</StackPanel>
</Grid>
The INotifyPropertyChanged is still necessary here because there needs to be a way for the UI to know that the model object has been updated.
I've a hard time understanding why ICommand.CanExecutes always contains the previous value instead of the new value if a nested property is used instead of a normal property.
The problem is described below and I seriously can't figure out a way to fix this besides using some form of "Facade" pattern where I create properties in the viewmodel and hook them to their corresponding property in the model.
Or use the damn CommandManager.RequerySuggested event. The reason this is not optimal is because the view presents over 30 commands, just counting the menu, and if all CanExecute updates every time something changes, it will take a few seconds for all menuitems / buttons to update. Even using the example down below with only a single command and button together with the command manager it takes around 500ms for the button to enable/disable itself.
The only reason I can think of is that the CommandParameter binding is not updated before the CanExecute is fired and then I guess there is nothing you can do about it.
Thanks in advance :!
For example
Let's say we've this basic viewmodel
public class BasicViewModel : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set {
this.name = value;
RaisePropertyChanged("Name");
Command.RaiseCanExecuteChanged();
}
}
private Project project;
public Project Project
{
get { return project; }
set {
if (project != null) project.PropertyChanged -= ChildPropertyChanged;
if (value != null) value.PropertyChanged += ChildPropertyChanged;
project = value;
RaisePropertyChanged("Project");
}
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e) {
Command.RaiseCanExecuteChanged();
}
public DelegateCommand<string> Command { get; set; }
public BasicViewModel()
{
this.Project = new Example.Project();
Command = new DelegateCommand<string>(this.Execute, this.CanExecute);
}
private bool CanExecute(string arg) {
return !string.IsNullOrWhiteSpace(arg);
}
private void Execute(string obj) { }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = null) {
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
and this model
public class Project : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
RaisePropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in my view I've this textbox and button.
<Button Content="Button" CommandParameter="{Binding Path=Project.Text}" Command="{Binding Path=Command}" />
<TextBox Text="{Binding Path=Project.Text, UpdateSourceTrigger=PropertyChanged}" />
It works, every time I type something in the textbox the CanExecute is invoked, BUT the parameter is always set to the previous value. Let say I write 'H' in the textbox, CanExecute is fired with parameter set to NULL. Next I write 'E', now the textbox contains "HE" and the CanExecute fires again. This time with the parameter set to 'H' only.
For some strange reason the parameter is always set to the previous value and when I check the Project.Text it's set to "HE" but parameter is still set to only 'H'.
If I now change the command parameter to
CommandParameter="{Binding Path=Name}"
and the Textbox.Text to
Text={Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"
everything works perfectly. The CanExecute parameter always contain the latest value and not the previous value.
The facade pattern you're talking about it standard WPF practice. The main problem with the way that you're doing it is that when events are raised, their subscribed event handlers execute in the order that they are subscribed. The line of code where you have:
if (value != null) value.PropertyChanged += ChildPropertyChanged;
This subscribes to the "PropertyChanged" Event of your "Project" class. Your UIElements are also subscribed to this same "PropertyChanged" event through your binding in the XAML. In short, your "PropertyChanged" event now has 2 subscribers.
The thing about events is that they fire in a sequence and what's happening in your code, is that when the event fires from your "Project.Text" it executes your "ChildPropertyChanged" event, firing your "CanExecuteChanged" event, which finally runs your "CanExecute" function(which is when you're seeing the incorrect parameter).
THEN, after that, your UIElements get their EventHandlers executed by that same event. And their values get updated.
It's the order of your subscriptions causing the problem. Try this and tell me if it fixes your problem:
public Project Project
{
get { return project; }
set {
if (project != null) project.PropertyChanged -= ChildPropertyChanged;
project = value;
RaisePropertyChanged("Project");
if (project != null) project.PropertyChanged += ChildPropertyChanged;
}
}
This is how I would have done this, and it works as expected. The only difference here is I'm using RelayCommand instead of DelegateCommand - they fundamentally have the same implementation so they should be interchangeable.
When the user enters the text and then clicks the button the execute method of the RelayCommand gets the expected text - simple.
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Column="0"
Grid.Row="0"
Text="{Binding Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="0"
Grid.Row="1"
Content="Test"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
Command="{Binding Path=TextCommand, Mode=OneWay}" />
</Grid>
ViewModel:
public sealed class ExampleViewModel : BaseViewModel
{
private string _text;
public ExampleViewModel()
{
TextCommand = new RelayCommand(TextExecute, CanTextExecute);
}
public string Text
{
get
{
return _text;
}
set
{
_text = value;
OnPropertyChanged("Text");
}
}
public ICommand TextCommand { get; private set; }
private void TextExecute()
{
// Do something with _text value...
}
private bool CanTextExecute()
{
return true;
}
}
I found this great attached property from swythan on the prism codeplex discussion forum that did the trick very well. Of course it does not answer why the command parameter is set to the previous value but it fixes the problem in a nice way.
The code is slightly modified from the source, enabling the possibility to use it on controls in a TabItem by calling HookCommandParameterChanged when the OnLoaded event is invoked.
public static class CommandParameterBehavior
{
public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty =
DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange",
typeof(bool),
typeof(CommandParameterBehavior),
new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged)));
public static bool GetIsCommandRequeriedOnChange(DependencyObject target)
{
return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty);
}
public static void SetIsCommandRequeriedOnChange(DependencyObject target, bool value)
{
target.SetValue(IsCommandRequeriedOnChangeProperty, value);
}
private static void OnIsCommandRequeriedOnChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ICommandSource))
return;
if (!(d is FrameworkElement || d is FrameworkContentElement))
return;
if ((bool)e.NewValue)
HookCommandParameterChanged(d);
else
UnhookCommandParameterChanged(d);
UpdateCommandState(d);
}
private static PropertyDescriptor GetCommandParameterPropertyDescriptor(object source)
{
return TypeDescriptor.GetProperties(source.GetType())["CommandParameter"];
}
private static void HookCommandParameterChanged(object source)
{
var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
propertyDescriptor.AddValueChanged(source, OnCommandParameterChanged);
// N.B. Using PropertyDescriptor.AddValueChanged will cause "source" to never be garbage collected,
// so we need to hook the Unloaded event and call RemoveValueChanged there.
HookUnloaded(source);
}
private static void UnhookCommandParameterChanged(object source)
{
var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
propertyDescriptor.RemoveValueChanged(source, OnCommandParameterChanged);
UnhookUnloaded(source);
}
private static void HookUnloaded(object source)
{
var fe = source as FrameworkElement;
if (fe != null)
{
fe.Unloaded += OnUnloaded;
fe.Loaded -= OnLoaded;
}
var fce = source as FrameworkContentElement;
if (fce != null)
{
fce.Unloaded += OnUnloaded;
fce.Loaded -= OnLoaded;
}
}
private static void UnhookUnloaded(object source)
{
var fe = source as FrameworkElement;
if (fe != null)
{
fe.Unloaded -= OnUnloaded;
fe.Loaded += OnLoaded;
}
var fce = source as FrameworkContentElement;
if (fce != null)
{
fce.Unloaded -= OnUnloaded;
fce.Loaded += OnLoaded;
}
}
static void OnLoaded(object sender, RoutedEventArgs e)
{
HookCommandParameterChanged(sender);
}
static void OnUnloaded(object sender, RoutedEventArgs e)
{
UnhookCommandParameterChanged(sender);
}
static void OnCommandParameterChanged(object sender, EventArgs ea)
{
UpdateCommandState(sender);
}
private static void UpdateCommandState(object target)
{
var commandSource = target as ICommandSource;
if (commandSource == null)
return;
var rc = commandSource.Command as RoutedCommand;
if (rc != null)
CommandManager.InvalidateRequerySuggested();
var dc = commandSource.Command as IDelegateCommand;
if (dc != null)
dc.RaiseCanExecuteChanged();
}
}
Source: https://compositewpf.codeplex.com/discussions/47338
I am having a binding issue I wasn't able to figure out for the past two days. I have thoroughly went through most of the relevant threads on SO, and I still wasn't able to pinpoint where my error lies.
The issue I'm having is with one of the textboxes in my program. The purpose of it is to show the file the user has selected from the file browser. I have bound the text property of it to a string called parameterFileSelected but the textbox never updates even though debugging seems to be showing that the iNotifyPropertyChanged is called and executed properly.
Please help me take a look at my code below if there are any mistakes in my code.
The textbox is part of an xaml called GenerateReports and this view is tied to the GenerateReportsViewModel as follows:
Code for setting datacontext to GenerateReportsViewModel
<Grid >
<Grid.DataContext>
<vm:GenerateReportsViewModel/>
</Grid.DataContext>
<Grid.ColumnDefinitions>
....
Code for TextBox. I have tried removing the Twoway mode, changing it to Oneway and removing the mode but there is no difference.
<TextBox Grid.Column="2" Grid.Row="1" Margin="5" Text="{Binding parameterFileSelected, Mode=Twoway, UpdateSourceTrigger=PropertyChanged}" ></TextBox>
To get the file browser and then to pass the selected file result to the GenerateReportsViewModel, this is the function in the codebehind file. The genviewmodel is initialized in the beginning of the codebehind file as GenerateReportsViewModel genViewModel = new GenerateReportsViewModel();
private void ParaFileButtonClick(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
DataContext = genViewModel;
genViewModel.updateParameterFileSelected(openFileDialog.FileName.ToString());
}
}
This is the code that's called in GenerateReportsViewModel to update the parameterFileSelected string the textbox is bound to.
class GenerateReportsViewModel : ViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get { return _parameterFileSelected; }
set { SetValue(ref _parameterFileSelected, value); }
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
Here is the ViewModelBase the viewmodel is attached to.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void SetValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (property != null)
{
if (property.Equals(value)) return;
}
OnPropertyChanged(propertyName);
property = value;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
EDIT
Working Solution after Applying Kevin's Suggestions
For simplicity sake, the Datacontext was set in the XAML.
<Grid>
<Grid.DataContext>
<vm:GenerateReportsViewModel x:Name="generateReportsViewModel"/>
</Grid.DataContext>
Then, I call the string the textbox was bound to, in the viewmodel directly from code behind.
private void ParaFileButtonClick(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
generateReportsViewModel.parameterFileSelected = openFileDialog.FileName.ToString();
}
}
The ViewModel now uses Kevin's ViewModelBase:
public class GenerateReportsViewModel : ViewModelBase
{
public string parameterFileSelected
{
get { return this.GetValue<string>(); }
set { this.SetValue(value); }
}
}
Thank you Kevin for your solution. Now my 2-day-long problem is solved.
I found out that my previous ViewModelBase was calling iNotifyPropertyChanged but somehow when the View was updated, the value was null instead.
I'm trying to understand why using the ref keyword in your viewModel. I learned a nice way to create the BaseViewModel from the Classon and Baxter book which you can find below. The view-model implements the INotifyPropertyChanged like you did. What you did with [CallerMemberName] is great, it's really magical the way we can reference to our properties thanks to it.
The view model uses a the dictionary to store its properties. It uses a pretty neat trick of looking through the dictionnary keys to see if we contain the string name of the property.Otherwise, we will return a default T value.
public class CommonBaseViewModel: INotifyPropertyChanged
{
private Dictionary<string, object> Values { get; set; }
protected CommonBaseViewModel()
{
this.Values = new Dictionary<string, object>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected T GetValue<T>([CallerMemberName] string name=null)
{
if (this.Values.ContainsKey(name))
{
return (T)this.Values[name];
}
else
{
return default(T);
}
}
protected void SetValue(object value, [CallerMemberName] string name = null)
{
this.Values[name] = value;
//notify my property
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected void OnPropertyChanged([CallerMemberName] string name=null)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
}
As for your GenerateReportViewModel, with the common view model that I provided you, your class then becomes :
public class GenerateReportsViewModel : CommonViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get { return _parameterFileSelected; }
set { SetValue(ref _parameterFileSelected, value); }
}
get
{
return this.GetValue<string>();
}
set
{
this.SetValue(value);
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
Oh before I forgot, I don't know if it was your intention, but your GenerateReportViewModel is private. This has some impact on your code. Don't forget that by defaut, classes are private!
As for your code behind, even though it could be consider bad practice, I recommend that you have a private field (OpenFileDialog _openFileDialog)that you construct while initializing your page. Because doing it each time your clicking your button is going to consume more data that you need your application to.
//EDIT
I have review my code,and it seemed that the property was not programmed correctly.
public class GenerateReportsViewModel : CommonViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get
{
return this.GetValue<string>();
}
set
{
this.SetValue(value);
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
More about my comment about constructing the page and binding the view model. While creating your page, you have to create the view-model for that page and then bind it to the data context.
I don't know what you do in your code, but I could provide with this sample such as
public GenerateReportView()
{
InitializeComponent();
//Some operations
var generateReportViewModel = new GenerateReportViewModel();
this.DataContext = generateReportViewModel;
}
How do i pass a bool from the viewmodel to the view and change the value.
For example in my viewmodel i have created a bool
public bool load
{
get { return m_load; }
set
{
m_load = value;
OnPropertyChanged(this, o => o.load);
}
}
I then have my SelectedVm code
public ViewModel SelectedVm
{
get { return _selectedVm; }
set
{
_selectedVm = value;
if (_selectedVm != null && load == true)
{
_selectedVm.Load();
}
Load = false;
OnPropertyChanged(this, o => o.SelectedVm);
}
}
In my View the SelectedVm is bound twice but only on one of the bindings do i want Load to be called, hence the need to change the bool load
So in my view if i have the following
<ListView Grid.Row="1" Name="Sample"
ItemsSource="{Binding Path=SampleViewModel}"
SelectedItem="{Binding SelectedVm, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True" Width="500">
</ListView>
How do i change the bool load to either true or false
All of the above are just quick samples, i think this is probably quite simple however i am not that used to WPF and am still learning. any advice would be great
Ok if you want to get value of Load in View and you want to do it in pure MVVM pattern then create DependencyProperty of bool type in View and Bind it to Load property of VM like
public partial class MainWindow : Window
{
public static readonly DependencyProperty LoadProperty = DependencyProperty.Register("MyCustom", typeof(bool), typeof(MainWindow), new PropertyMetadata(new PropertyChangedCallback(LoadPropertyChangedCallback)));
public bool Load
{
get
{
return (bool)this.GetValue(LoadProperty) ;
}
set
{
this.SetValue(LoadProperty, value);
}
}
static void LoadPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Do your load stuff here
}
public MainWindow()
{
InitializeComponent();
this.SetBinding(LoadProperty, new Binding("load"));
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
load = true;
}
bool m_load;
public bool load
{
get { return m_load; }
set
{
m_load = value;
OnPropertyChanged("load");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
The code you published ensures that Load() is being called once, even with multiple bindings.
If I understand your real question correctly, you are actually asking for a way to make sure that each SelectedVM is calling Load() function once, and only once. Right?
if that so, you need to add a bool property to the ViewModel class, instead of the main class, that's all.
And then:
public ViewModel SelectedVm
{
get { return _selectedVm; }
set
{
_selectedVm = value;
if (_selectedVm != null && _selectedVm.load == true)
{
_selectedVm.Load();
_selectedVm.load = false;
}
OnPropertyChanged(this, o => o.SelectedVm);
}
}
you can keep your XAML as it is.