MVVM Binding not working when change from event firing - c#

I make a simple MVVM sample. I have main window with two user control pages. The main window have two event to change the view to two user control.
This is my main window XAML
<Window
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:local="clr-namespace:MVVC_Binding"
xmlns:views="clr-namespace:MVVC_Binding.Views"
xmlns:viewModel="clr-namespace:MVVC_Binding.ViewModels"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" x:Class="MVVC_Binding.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:Page1ViewModel}">
<views:Page1 />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:Page2ViewModel}">
<views:Page2/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="Page1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding NavCommand}" CommandParameter="page1"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<TextBlock Text="Page2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding NavCommand}" CommandParameter="page2"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</StackPanel>
<DockPanel Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Gainsboro">
<Grid x:Name="container" Background="Gainsboro" VerticalAlignment="Top">
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</DockPanel>
</Grid>
</Window>
I am using the BindableBase class for my view model. This is my BindableBase class
namespace MVVC_Binding.Utilities
{
public class BindableBase : INotifyPropertyChanged
{
/// <summary>
/// Interface implementation
/// </summary>
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
{
// Check for current set member and the new value
// If they are the same, do nothing
if (object.Equals(member, val)) return;
member = val;
// Invoke the property change event
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
In my main view model, just simple click event to change the view binding
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel
{
get { return _CurrentViewModel; }
set
{
SetProperty(ref _CurrentViewModel, value);
}
}
private void OnNav(string destination)
{
switch (destination)
{
case "page2":
CurrentViewModel = page2;
break;
default:
CurrentViewModel = page1;
break;
}
}
The problem is in user control Page 2, when it is display, and the event in side of it does not change the TextBlock binding value, but the text can change during the view model constructor event.
Here is my page 2 XAML
<UserControl x:Class="MVVC_Binding.Views.Page2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MVVC_Binding.Views"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<TextBlock Name="txtPage2" Text="{Binding Page2Text}"></TextBlock>
<TextBlock Name="btn" Text="Click Button">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding BtnCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</StackPanel>
</UserControl>
And here is the Page2ViewModel
namespace MVVC_Binding.ViewModels
{
public class Page2ViewModel : BindableBase
{
public MyICommand<string> BtnCommand { get; private set; }
public Page2ViewModel()
{
BtnCommand = new MyICommand<string>(OnBtnClick);
Page2Text = "Just a test";
}
private void OnBtnClick(string obj)
{
Page2Text = "Changing by button click";
}
private string _page2Text;
public string Page2Text
{
get { return _page2Text; }
set
{
_page2Text = value;
SetProperty(ref _page2Text, value);
}
}
}
}
Can you please see what I am doing wrong? Thanks so much

If I understand correctly, you're asking why the code in this function doesn't seem to have an effect on the view:
private void OnBtnClick(string obj)
{
_page2Text = "Changing by button click";
}
The problem is that you are changing the underlying _page2Text member, but in order for WPF to detect this change, you must use the Page2Text property, like this:
private void OnBtnClick(string obj)
{
Page2Text = "Changing by button click";
}
The specific part of your code that is indicating the property change to WPF is the OnPropertyChanged method in your BindableBase class.

Thanks everyone, I manage to solve it by updating the class BindableBase. The updated
namespace SilentUpdate.Utilities
{
public class BindableBase : INotifyPropertyChanged
{
/// <summary>
/// Interface implementation
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
{
// Check for current set member and the new value
// If they are the same, do nothing
// if (object.Equals(member, val)) return;
member = val;
// Invoke the property change event
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
So I comment out the check for the Object.Equals and force it to run for every value

Related

WPF Binding and Observable Model fail

In a simple trying-to-learn-WPF experiment I'm trying to bind a property ("InternalName") of an instance of MyModel to the contents of TextBlock "MainWindowTextBlock". Clicking the ``ChangeNameButton" changes the InternalName property of mymodel, but that property change never makes it through to the TextBlock. Nothing happens. What am I doing wrong?
XMAL
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:local="clr-namespace:UserControlExperiments"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Grid.Row ="0">
<Button Width="100" Height="20" Name="ChangeName" Content="Change the Name" Click="ChangeNameButtonClick"/>
<TextBlock Text=""/>
<TextBlock Name="MainWindowTextBox" Width="100" Height="20" Text="{Binding Path = mymodel.InternalName, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</Window>
CODE BEHIND
public partial class MainWindow : Window
{
public MyModel mymodel;
public MainWindow()
{
InitializeComponent();
DataContext = this.DataContext;
mymodel = new MyModel("The old name");
}
private void ChangeNameButtonClick(object sender, RoutedEventArgs e)
{
mymodel.InternalName = "A new name!";
}
}
public class MyModel : INotifyPropertyChanged
{
private string internalname;
public event PropertyChangedEventHandler PropertyChanged;
public MyModel(string nm)
{
InternalName = nm;
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string InternalName
{
get { return internalname; }
set
{
if (internalname != value)
{
internalname = value;
OnPropertyChanged("InternalName");
}
}
}
}
}
The following markup tries to bind to a property named "mymodel" of the current DataContext of the TextBlock, which is inherited from the parent window:
<TextBlock Name="MainWindowTextBox"
Text="{Binding Path = mymodel.InternalName}"/>
So you need to set the DataContext of the window to itself:
DataContext = this;
And you also need to make mymodel a public property since you cannot bind to fields:
public MyModel mymodel { get; }
Then it should work but you probably also want to change the name of the property to comply with the C# naming standards.
You can also remove Mode=TwoWay from the binding. It makes no sense for a TextBlock.

How to change the text in my Textblock in MainWindow using iCommand?

Basically I have one textblock and 2 button, I want the textblock Text to change according to the button I click. For example, if I click on the Button 1, it will display, "Button 1 is click"
if I click on button 2, it will display "Button 2 is click"
This is my ViewModel
namespace ICommandProject2.ViewModel
{
class ViewModel
{
public ICommand myCommand { get; set; }
public ViewModel()
{
myCommand = new myCommand(ExecutedMethod);
}
private void ExecutedMethod (object parameter)
{
MainWindow m = new MainWindow();
m.txtBlock.Text = "Button 1 is click";
}
}
}
This my Command Class
namespace ICommandProject2.Command
{
class myCommand : ICommand
{
Action<object> actionExecuted;
public myCommand(Action<object> ExecutedMethod)
{
actionExecuted = ExecutedMethod;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
actionExecuted(parameter);
}
}
}
This is my Mainwindow.xaml
<Window x:Class="ICommandProject2.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:local="clr-namespace:ICommandProject2.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:ViewModel x:Key="vm"/>
</Window.Resources>
<Grid>
<Button x:Name="btnOne" Content="Button 1" Command="{Binding myCommand, Source={StaticResource vm}}" HorizontalAlignment="Left" Margin="273,232,0,0" VerticalAlignment="Top" Width="75" FontSize="18"/>
<TextBlock x:Name="txtBlock" HorizontalAlignment="Left" Margin="273,89,0,0" TextWrapping="Wrap" Text="This is a textblock" VerticalAlignment="Top" FontSize="36"/>
<Button x:Name="btnTwo" Content="Button 2" HorizontalAlignment="Left" Margin="495,232,0,0" VerticalAlignment="Top" Width="75" FontSize="18" />
</Grid>
</Window>
When I click on the button, nothing is happening, what should I change?
use binding to set TextBlock text.
create a property fro binding in a view model and change that property in command handler:
class ViewModel : INotifyPropertyChanged
{
public ICommand myCommand { get; set; }
private string _title = "This is a textblock";
public string Title { get { return _title; } }
public ViewModel()
{
myCommand = new myCommand(ExecutedMethod);
}
private void ExecutedMethod (object parameter)
{
_title = "Button 1 was clicked";
OnPropertyChanged("Title");
}
}
I omitted that part, but you should implement INotifyPropertyChanged to notify view about changes in a view model.
use Binding in a view:
<TextBlock x:Name="txtBlock" Text="{Binding Title}" ...
(command as it is now doesn't work becuase it creates an new instance of Window (MainWindow m = new MainWindow();) instead of working with open Window)

Change Enabled State of Button immediately on any change in a TextBox?

I use data binding and command binding to set the enabled state of a button, depending on whether a particular string property has a value or not. Or you might say, I have a mandatory TextBox, and I want the user to not be able to click Ok before at least 1 character has been entered.
My code does exactly that, only that the enabled state of the button is not updated before the TextBox is unfocused, e.g. by pressing the Tab key. I want this to happen immediately, on any change of the TextBox content. How can I achieve this? Without breaking out of MVVM, of course!
View:
<Window x:Class="Gebietsmanager.GebietBearbeitenDlg.View"
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:local="clr-namespace:Gebietsmanager.GebietBearbeitenDlg"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:ViewModel}"
Title="Gebiet bearbeiten" Height="110" Width="300" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" ResizeMode="NoResize">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Margin="8,8,0,0">Name:</Label>
<TextBox Grid.Column="1" Text="{Binding Name}" Margin="8,8,8,0"/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="8,8,0,0">
<Button IsDefault="True" Command="{Binding Commit}">Ok</Button>
<Button Command="{Binding Rollback}" Margin="8,0,0,0">Reset</Button>
<Button IsCancel="True" Margin="8,0,0,0">Cancel</Button>
</StackPanel>
</Grid>
</Window>
ViewModel:
using System.ComponentModel;
namespace Gebietsmanager.GebietBearbeitenDlg
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel(Gebiet gebiet)
{
_gebiet = gebiet;
_gebietCopy = new Gebiet();
Helpers.CopyPropValues(_gebietCopy, gebiet);
Commit = new Command(
() => Helpers.CopyPropValues(_gebiet, _gebietCopy),
() => !string.IsNullOrEmpty(_gebietCopy.Name));
Rollback = new Command(DoRollback);
}
private readonly Gebiet _gebiet;
private readonly Gebiet _gebietCopy;
private void DoRollback()
{
Helpers.CopyPropValues(_gebietCopy, _gebiet);
OnPropertyChanged();
}
public string Name
{
get { return _gebietCopy.Name; }
set
{
if (_gebietCopy.Name != value)
{
_gebietCopy.Name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public Command Commit { get; private set; }
public Command Rollback { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Command implementation:
using System;
using System.Windows.Input;
namespace Gebietsmanager
{
public sealed class Command : ICommand
{
public Command(Action executeAction, Func<bool> canExecutePredicate = null)
{
_executeAction = executeAction;
_canExecutePredicate = canExecutePredicate;
}
private readonly Action _executeAction;
private readonly Func<bool> _canExecutePredicate;
public void Execute(object parameter)
{
_executeAction?.Invoke();
}
public bool CanExecute(object parameter)
{
return _canExecutePredicate?.Invoke() ?? true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
You need to set UpdateSourceTrigger=PropertyChanged in your binding,
Example with MVVMLight:
XAML
<Window x:Class="WpfApplication2.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:local="clr-namespace:WpfApplication2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Window.DataContext>
<local:MyModel />
</Window.DataContext>
<Grid>
<StackPanel>
<TextBlock Text="Name" />
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Go !" IsEnabled="{Binding IsReady}" />
</StackPanel>
</Grid>
</Window>
Code
internal class MyModel : ViewModelBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
Set(() => Name, ref _name, value);
RaisePropertyChanged(() => IsReady);
}
}
public bool IsReady
{
get { return !string.IsNullOrEmpty(Name); }
}
}

OnPropertyChanged is null in WP8

AcctionCommand
My problem is that for the change of the variable are reflected in the user interface, the value of PropertyChanged should be different from null (Left) because I'm assigning a value.
I have a generic class to handle the click events of the buttons
public class ActionCommand : ICommand
{
Action action;
public ActionCommand(Action action)
{
this.action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
action();
}
}
INotifyPropertyChanged
I have a class NotificationEnabledObject to notify the Left value change to the user interface in PropertyChange which always returns null, I do not know what I'm doing wrong??
public class NotificationEnabledObject : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
ViewModel
I have a ViewModel class that has the Left property.
public class WordsViewModel : NotificationEnabledObject
{
string left;
public string Left
{
get { return left; }
set
{
left = value;
OnPropertyChanged();
}
}
MainPage.xaml
<phone:PhoneApplicationPage
x:Class="SpeechRecognition.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:core="http://schemas.microsoft.com/client/2007"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
xmlns:vm="clr-namespace:SpeechRecognition.ViewModels"
shell:SystemTray.IsVisible="True"
DataContext="{Binding Source={StaticResource ViewModel}}">
<StackPanel>
<Grid Height="Auto" Width="Auto">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="40" x:Name="txtWord" Height="Auto" VerticalAlignment="Center" HorizontalAlignment="center">
<core:Run x:Name="rLeft" Text="{Binding Left, Mode=TwoWay}" />
</TextBlock>
<Button Name="ReadNow" Grid.Row="1" Height="Auto" Content="Read Now" VerticalAlignment="Bottom" Click="ReadNow_Click">
</Button>
</Grid>
</StackPanel>
</phone:PhoneApplicationPage>
This action is the click event of the button on the user interface, I do not know how to make this work, OnPropertyChanged is always null, I want to change the value of the variable in interface Left repeatedly while running the program
ActionCommand getWordsCommand;
public ActionCommand GetWordsCommand
{
get
{
if (getWordsCommand == null)
{
getWordsCommand = new ActionCommand(() =>
{
Left = 10;
}
});
}
return getWordsCommand;
}
}
You have a few problems
First, your data context needs to be set in your view code behind
this.Datacontext = //your view model
Second, Your WordsViewModel class needs to implement INotifyPropertyChanged
Third your OnPropertyChanged Signature is wrong.
It should look like the below example
Also you shouldn't be using your actual PropertChanged event handler. Its not thread safe.
Instead, clone it within your OnPropertyChanged event
void OnPropertyChanged(String prop){
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null){
PropertChanged(this,new PropertyChangedEventArgs(prop));
}
}
Finally in your Left property invoke OnPropertyChanged("Left");

WPF DataBinding not updating?

I have a project, where I bind a checkbox's IsChecked property with a get/set in the codebehind. However, when the application loads, it doesn't update, for some reason. Intrigued, I stripped it down to its basics, like this:
//using statements
namespace NS
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private bool _test;
public bool Test
{
get { Console.WriteLine("Accessed!"); return _test; }
set { Console.WriteLine("Changed!"); _test = value; }
}
public MainWindow()
{
InitializeComponent();
Test = true;
}
}
}
XAML:
<Window x:Class="TheTestingProject_WPF_.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Viewbox>
<CheckBox IsChecked="{Binding Path=Test, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Viewbox>
</Grid>
And, lo and behold, when I set it to true, it did not update!
Anyone can come up with a fix, or explain why?
Thanks, it'd be appreciated.
In order to support data binding, your data object must implement INotifyPropertyChanged
Also, it's always a good idea to Separate Data from Presentation
public class ViewModel: INotifyPropertyChanged
{
private bool _test;
public bool Test
{ get { return _test; }
set
{
_test = value;
NotifyPropertyChanged("Test");
}
}
public PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
<Window x:Class="TheTestingProject_WPF_.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Viewbox>
<CheckBox IsChecked="{Binding Path=Test, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Viewbox>
</Grid>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel{Test = true};
}
}

Categories