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");
Related
My idea is to have a window with 2 content controls changing to different views. For example, my app may start with a welcome image in one content control and enter/exit button in other. On enter button click, I want to change the welcome view to some other view and the enter/exit buttons to 3 other buttons.
My current problem is, trying to change the welcome view by clicking the enter button does not seem to work if I put it in a user control and host it in content control. My guess is because I am actually creating a new instance of my MainViewModel instead of referencing the existing one, but I am not entirely sure.
How can I make it so, when I click on my button in BottomPanelTwoButtonsView my HomeView changes to TestView?
There are 4 views in total:
HomeView - the welcome screen
TestView - some other view
BottomPanelTwoButtonsView - enter/exit buttons
BottomPanelThreeButtonsView - some 3 other buttons
Here's the basic code I have:
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow app = new MainWindow();
MainViewModel context = new MainViewModel();
app.DataContext = context;
app.Show();
}
}
MainWindow.xaml
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:localViewModels="clr-namespace:MyApp.ViewModels"
xmlns:localViews="clr-namespace:MyApp.Views"
mc:Ignorable="d"
Name="RootWindow"
WindowStyle="None" ResizeMode="NoResize"
Topmost="True"
WindowStartupLocation="CenterScreen"
WindowState="Maximized"
Background="{StaticResource BackgroundSolidColorBrush}"
ToolBarTray.IsLocked="True"
NumberSubstitution.Substitution="European"
Title="MyApp" Height="1920" Width="1080">
<Window.Resources>
<DataTemplate DataType="{x:Type localViewModels:HomeViewModel}">
<localViews:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:TestViewModel}">
<localViews:TestView />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:BottomPanelTwoButtonsViewModel}">
<localViews:BottomPanelTwoButtons />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:BottomPanelThreeButtonsViewModel}">
<localViews:BottomPanelThreeButtons />
</DataTemplate>
</Window.Resources>
<DockPanel Background="White">
<ContentControl Content="{Binding CurrentBottomPanelViewModel}" DockPanel.Dock="Bottom" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Height="96" Width="1080">
</ContentControl>
<ContentControl Content="{Binding CurrentPageViewModel}" HorizontalAlignment="Stretch" VerticalAlignment="Top" HorizontalContentAlignment="Stretch" Height="1824" Width="1080">
</ContentControl>
</DockPanel>
</Window>
MainViewModel.cs
public class MainViewModel : ObservableObject
{
#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private IBottomPanelViewModel _currentBottomPanelViewModel;
private ObservableCollection<IPageViewModel> _pageViewModels;
#endregion
public MainViewModel()
{
CurrentPageViewModel = new HomeViewModel();
CurrentBottomPanelViewModel = new BottomPanelTwoButtonsViewModel();
//CurrentBottomPanelViewModel = new BottomPanelThreeButtonsViewModel();
PageViewModels.Add(new ScreeningsScheduleViewModel());
PageViewModels.Add(new TestViewModel());
PageViewModels.Add(CurrentPageViewModel);
}
#region Properties / Commands
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);
}
return _changePageCommand;
}
}
public ObservableCollection<IPageViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
{
_pageViewModels = new ObservableCollection<IPageViewModel>();
}
return _pageViewModels;
}
set
{
_pageViewModels = value;
OnPropertyChanged("PageViewModels");
}
}
public IPageViewModel CurrentPageViewModel
{
get => _currentPageViewModel;
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
public IBottomPanelViewModel CurrentBottomPanelViewModel
{
get => _currentBottomPanelViewModel;
set
{
if (_currentBottomPanelViewModel != value)
{
_currentBottomPanelViewModel = value;
OnPropertyChanged("CurrentBottomPanelViewModel");
}
}
}
#endregion
#region Methods
private void ChangeViewModel(IPageViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
{
PageViewModels.Add(viewModel);
}
CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
}
#endregion
}
BottomPanelTwoButtonsView.xaml
<UserControl x:Class="MyApp.Views.BottomPanelTwoButtons"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mvm="clr-namespace:MyApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="1080">
<UserControl.DataContext>
<mvm:MainViewModel/>
</UserControl.DataContext>
<Grid Height="96" Background="White" Width="1080">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Column="0"
Content="1"
Command="{Binding DataContext.ChangePageCommand, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type mvm:MainViewModel}}, Mode=TwoWay}"
CommandParameter="{Binding PageViewModels[2]}"
IsEnabled="True"
Style="{StaticResource RoundCornerButtonCyanFilledBig}"
/>
<Button Grid.Column="1" Style="{StaticResource RoundCornerButtonMagentaFilledBig}"/>
</Grid>
</UserControl>
IPageViewModel.cs
public interface IPageViewModel
{
string Name { get; }
}
ObservableObject.cs
public abstract class ObservableObject : INotifyPropertyChanged
{
[Conditional("DEBUG")]
[DebuggerStepThrough]
public virtual void VerifyPropertyName(string propertyName)
{
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (!ThrowOnInvalidPropertyName)
{
Debug.Fail(msg);
}
else
{
throw new Exception(msg);
}
}
}
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
public virtual void RaisePropertyChanged(string propertyName)
{
VerifyPropertyName(propertyName);
OnPropertyChanged(propertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
RelayCommand.cs
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute ?? throw new ArgumentNullException("execute");
_canExecute = canExecute;
}
[DebuggerStepThrough]
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
}
I guess that your problem is within the BottomPanelTwoButtonsView.xaml
There you have the following lines:
<UserControl.DataContext>
<mvm:MainViewModel/>
</UserControl.DataContext>
So you're overwriting your datacontext which is defined in the Window.Resources of the MainWindow
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
I have a Window:
<Window x:Class="ClientApp.Views.ModalWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="" Height="332" Width="536" >
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0">
<ContentControl Content="{Binding Path=InlaidViewModel}" Margin="0"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
</Window>
At run time, the InlaidViewModel binding on the ContentControl is set based on other values in the application. How can I set the MinHeight and MinWidth on the Window to the same values on the embedded control when it's bound? For example:
<Window x:Class="Roryap.BillCalendar.ClientApp.Views.ModalWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="" Height="332" Width="536"
MinHeight="{Binding **what goes here**}" MinWidth="{Binding **what goes here**}">
I know I could add properties to the underlying view model for my Window to bind to for those values, but I'm not sure I want to do that, and this more of a curiosity.
The question is: if I don't want to have view model properties for MinHeight and MinWidth for my window, is there a way to inherit those values from an embedded control which is bound at run time?
If I understand your requirements correctly you should be able to bind to the MinHeight and MinWidth properties of the Content of the ControlControl like this:
<Window x:Class="ClientApp.Views.ModalWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="" Height="332" Width="536"
MinHeight="{Binding Path=Content.MinHeight, ElementName=cc}"
MinWidth="{Binding Path=Content.MinWidth, ElementName=cc}">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0">
<ContentControl x:Name="cc" Content="{Binding Path=InlaidViewModel}" Margin="0"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
This assumes that whatever object the InlaidViewModel property returns has MinHeight and MinWidth properties of type System.Double:
public partial class ModalWindow : Window
{
private readonly WindowViewModel _viewModel;
public ModalWindow()
{
InitializeComponent();
_viewModel = new WindowViewModel();
DataContext = _viewModel;
Loaded += async (s, e) =>
{
_viewModel.InlaidViewModel = new InlaidViewModel();
//wait 2 seconds before setting the MinHeight property
await Task.Delay(2000);
_viewModel.InlaidViewModel.MinHeight = 500;
};
}
}
public class WindowViewModel : INotifyPropertyChanged
{
private InlaidViewModel _inlaidViewModel;
public InlaidViewModel InlaidViewModel
{
get { return _inlaidViewModel; }
set { _inlaidViewModel = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class InlaidViewModel : INotifyPropertyChanged
{
private double _minHeight;
public double MinHeight
{
get { return _minHeight; }
set { _minHeight = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
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); }
}
}
I have a List<> of objects that I have bound to a ListBox. I then want to bind properties of the SelectedValue to various TextBoxes. The behavior is very screwy though.
When binding the Name (a string) that is used as the DisplayMember for the ListBox, it doesn't update the ListBox and if I try to refresh the binding on the TextChanged event, it doesn't update until the selection changes and then it has problems switching the selection.
When binding Balance (a decimal) it changes all of them (or, possibly, the change is being applied when I change the selection, but it is actually changing the data, not just not updating).
To be clear, I am using C#.NET and not ASP.
Assuming WPF, a quick sample:
<Window x:Class="WpfApplication1.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>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListBox x:Name="list"
ItemsSource="{Binding}"
DisplayMemberPath="Name"/>
<TextBox Text="{Binding ElementName=list, Path=SelectedItem.Name}"
Grid.Row="1"/>
<TextBox Text="{Binding ElementName=list, Path=SelectedItem.Val}"
Grid.Row="2" />
</Grid>
</Window>
namespace WpfApplication1 {
public class Thing : INotifyPropertyChanged {
private string _name;
private double _val;
public string Name {
get { return _name; }
set {
_name = value;
OnPropertyChanged("Name");
}
}
public double Val {
get { return _val; }
set {
_val = value;
OnPropertyChanged("Val");
}
}
protected void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null) {
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new List<Thing> { new Thing { Name = "A", Val = 1.0 }, new Thing { Name = "B", Val = 2.0 } };
}
}
}
I found the solution at Switch on the Code. Basically, I need to use the BindingList collection instead of just List.