I am puzzled by a (I thought) simple thing to implement; make a UI element visible depending on a binding to a view model. I use the mvvmlight framework. When the binding (boolean) is set to true the visibility binding does not react to the change.
XAML:
<Button
Command="{Binding NavigationCommand}" CommandParameter="{StaticResource Back}"
Visibility="{x:Bind (Visibility) ViewModel.ShowNavigationButtons}">
<Image Source="../../../Resources/NavigateBack.PNG"/>
</Button>
Code behind:
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
DataContext = new MainViewModel();
}
public MainViewModel ViewModel => DataContext as MainViewModel;
}
ViewModel:
public class MainViewModel : ViewModelBase
{
private bool _showNavigationButtons;
public RelayCommand BrakingCommand { get; }
public bool ShowNavigationButtons
{
get => _showNavigationButtons;
set { Set(() => ShowNavigationButtons, ref _showNavigationButtons, value); }
}
public MainViewModel()
{
BrakingCommand = new RelayCommand(() =>
{
ShowNavigationButtons = true;
NavigationCommand.RaiseCanExecuteChanged();
});
}
}
I also tried to bind "the WPF way" :
Visibility="{Binding ShowNavigationButtons, Converter{StaticResource BoolToVisibilityConverter}">
But that results in the exact same problem; the view doesn't react on the changed property.
Help is much appreciated,
For the love of....
The problem was that the default mode for a binding is onetime. Spend a freaking hour to figure that out. When I declare the binding as follows it works as expected...
Visibility="{x:Bind (Visibility) ViewModel.ShowNavigationButtons, Mode=OneWay}">
I hope that this helps somebody else one day who's pulling his hair out...
Related
In WPF I have been trying to figure out how to keep a views dependency property and one of it's view model's properties in sync for a while now without any luck. I have done a fair amount research into the subject but none of the suggested solutions are working for me and I was hoping someone could help me find what I am missing.
I attempted many of the things suggested in this post, Twoway-bind view's DependencyProperty to viewmodel's property?, because of all the things I read it looked to be the most promising, but was never able to get the results I was looking for.
I have written a simple program to demonstrate that issue I am having. In it I set the the property IntValue in MainWindowViewModel to 2 and then Bind it to a dependency property created in the UserControl IncrementIntView. Then when I push the button in IncrementIntView it increases the value of IntValue by one. This all works fine inside the UserControl IncrementIntView but I can't figure out how to send the updated IntValue back to MainWindowViewModel, it stays set to 2.
IncrementIntView.xaml.cs
public partial class IncrementIntView : UserControl
{
public int IntValue
{
get { return (int)GetValue(IntValueProperty); }
set { SetValue(IntValueProperty, value); }
}
public static readonly DependencyProperty IntValueProperty =
DependencyProperty.Register("IntValue", typeof(int), typeof(IncrementIntView),
new PropertyMetadata(-1, new PropertyChangedCallback(IntValueChanged)));
private static void IntValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
IncrementIntView detailGroup = dependencyObject as IncrementIntView;
if (e.NewValue != null)
{
detailGroup.ViewModel.IntValue = (int)e.NewValue;
}
}
public IncrementIntView()
{
InitializeComponent();
}
}
IncrementIntViewModel.cs
public class IncrementIntViewModel : ViewModelBase
{
private int intValue;
public int IntValue
{
get { return intValue; }
set { SetProperty(ref intValue, value); }
}
public IncrementIntViewModel()
{
incrementIntCommand = new Command(IncrementInt);
}
private Command incrementIntCommand;
public Command IncrementIntCommand { get { return incrementIntCommand; } }
public void IncrementInt()
{
IntValue++;
}
}
IncrementIntView.xaml
<UserControl.DataContext>
<local:IncrementIntViewModel x:Name="ViewModel" />
</UserControl.DataContext>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding IntValue}" />
<Button Content="Increment" Command="{Binding IncrementIntCommand}" Width="75" />
</StackPanel>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
private int intValue = 2;
public int IntValue
{
get { return intValue; }
set { SetProperty(ref intValue, value); }
}
}
MainWindow.xaml
<Window.DataContext>
<local:MainWindowViewModel x:Name="ViewModel"/>
</Window.DataContext>
<Grid>
<StackPanel Margin="10">
<local:IncrementIntView IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
<Label Content="{Binding IntValue}" />
</StackPanel>
</Grid>
I can see that your code is passing the IntValue from MainWindowViewModel's property to IncrementIntView's dependency property to IncrementIntViewModel's property.
The increment button is updating the IncrementIntViewModel's IntValue property. Unfortunately, whatever happens in the IncrementIntViewModel is not being reflected back to the IncrementIntView's IntValue dependency property. The TwoWay Mode is not between IncrementIntView's dependency property and IncrementIntViewModel's property, but it is between MainWindowViewModel's property to IncrementIntView's dependency property.
The easy solution: Bind the MainWindow's Label to IncrementIntViewModel's IntValue property without bothering the View's property.
<local:IncrementIntView x:Name="iiv" IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
<Label Content="{Binding DataContext.IntValue, ElementName=iiv}" />
<!--You need to specify DataContext.IntValue, because you have same name for both view's dependency property and viewmodel's property-->
Here you can see that MainWindowViewModel's IntValue is not that important, because it just passes the value to IncrementIntViewModel once and never have the value updated ever.
The other solution: You need to trigger value change back to MainViewModel's property.
First thing first, there is no connection between MainViewModel and IncrementIntViewModel. One solution is to make the MainViewModel to be singleton, so that when increment is done inside the IncrementIntViewModel, you want to update the MainViewModel's property as well.
In MainViewModel.cs
public static MainWindowViewModel SingletonInstance { get; set; }
public MainWindowViewModel()
{
if (SingletonInstance == null)
{
SingletonInstance = this;
}
}
In IncrementIntViewModel.cs
public void IncrementInt()
{
IntValue++;
MainWindowViewModel.SingletonInstance.IntValue = IntValue;
}
The other other solution: Similar to the above solution, but we don't need to make Singleton instance of MainWindowViewModel, because MainWindow is singleton to begin with.
In IncrementIntViewModel.cs
public void IncrementInt()
{
IntValue++;
((MainWindowViewModel)App.Current.MainWindow.DataContext).IntValue = IntValue;
}
If your intention is to update the IntValue from IncrementViewModel's property to IncrementView's dependency property, then you might ask why you need to do this, because MVVM is supposed to separate between V and VM. V should be looking to VM, but not the other way around.
I found a lot of examples on how to bind the IsChecked property of a WPF checkbox to a boolean property, if both belong to the same Window class. I want to do a different thing:
I have the main window (excerpt):
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private readonly SoundFx _soundFx = new SoundFx();
private void _StartNewGame()
{
_soundFx.GameStarted();
}
}
Then I have the SoundFx class (excerpt):
public class SoundFx : DependencyObject
{
public void GameStarted()
{
if (Enabled)
{
_PlayGameStartedSound();
}
}
public bool Enabled
{
get { return (bool) GetValue(EnabledProperty); }
set { SetValue(EnabledProperty, value); }
}
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.Register("Enabled", typeof(bool),
typeof(SoundFx), new UIPropertyMetadata(false));
}
And I have the XAML (excerpt):
<Grid>
<CheckBox IsChecked="{Binding ElementName=_soundFx, Path=Enabled}" x:Name="checkBoxSoundFx" Content="Sound FX" HorizontalAlignment="Right" Margin="0,0,10,10" VerticalAlignment="Bottom"/>
</Grid>
To be honest, I'm new to WPF and don't know exactly what I'm doing. What I'm trying to achieve is that the value of _soundFx.Enabled be changed when the user clicks on the checkBoxSoundFx element, without using any event handlers like Checked or Unchecked. This should be possible with data binding, shouldn't it?
First you need to create
public SoundFx _soundFx { get; set; }
as public property, because you cannot bind to private field
public MainWindow()
{
InitializeComponent();
_soundFx = new SoundFx();
}
And from xaml you need to bind like:
<CheckBox IsChecked=
"{Binding RelativeSource=
{RelativeSource Mode=FindAncestor,AncestorType=Window},
Path=_soundFx.Enabled}"}"
x:Name="checkBoxSoundFx"
Content="Sound FX"
HorizontalAlignment="Right"
Margin="0,0,10,10"
VerticalAlignment="Bottom"/>
You were close, you need a property to bind to and you need to set the DataContext if you didn't do it:
public partial class MainWindow
{
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
private readonly SoundFx _soundFx = new SoundFx();
public SoundFx {get {return _soundFx;}}
private void _StartNewGame()
{
_soundFx.GameStarted();
}
}
You then need to bind to this property (and set the mode to OneWayToSource if you only need to set the property, never update the CheckBox according to the property value):
<Grid>
<CheckBox IsChecked="{Binding Path=SoundFx.Enabled, Mode=OneWayToSource}" x:Name="checkBoxSoundFx" Content="Sound FX" HorizontalAlignment="Right" Margin="0,0,10,10" VerticalAlignment="Bottom"/>
</Grid>
By the way I'm not sure why you SoundFx is a DependencyObject and why your Enabled property is a DependencyProperty. A simple property would work aswell in this particular example.
DependencyProperties are useful when you want to set them in a Style or animate them with a Storyboard for example, you don't seem to be in this case. I think SoundFx should inherit DependencyObject and Enabled should be a simple property (This is an opinion I make knowing very little about your project though).
As I've managed to grow more experienced in WPF in the meantime, I would now say that my question itself was wrong. In order to avoid confusion in binding and unnecessary dependencies between view and model, I would now always prefer MVVM for cases like this.
Example: https://codereview.stackexchange.com/questions/124361/mvvm-am-i-doing-it-right
I'm very new to MVVM and bindings and I'm trying to learn to work with it.
I run into the problem of binding my viewmodel to the view in particular binding an observable collection to a listbox.
this is what my viewmodel looks like:
namespace MyProject
{
using Model;
public class NetworkViewModel: INotifyPropertyChanged
{
private ObservableCollection<Person> _networkList1 = new ObservableCollection<Person>();
public ObservableCollection<Person> NetworkList1 //Binds with the listbox
{
get { return _networkList1; }
set { _networkList1 = value; RaisePropertyChanged("_networkList1"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public NetworkViewModel()
{
_networkList1 = new ObservableCollection<Person>()
{
new Person(){FirstName="John", LastName="Doe"},
new Person(){FirstName="Andy" , LastName="Boo"}
};
}
}
in the view I have
namespace MyProject
{
public partial class Networking : Window
{
public Networking()
{
InitializeComponent();
this.DataContext = new NetworkViewModel();
lb1.ItemsSource = _networkList1;
}
}
}
and in the XAML I have
<ListBox x:Name="lb1" HorizontalAlignment="Left" ItemsSource="{Binding NetworkList1}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock >
<Run Text="{Binding Path=FirstName}"/>
<Run Text="{Binding Path=LastName}"/>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It seems like you might have a typo in your view model.
RaisePropertyChanged("_networkList1");
You want to raise the property changed notification for the public property not the private variable.
RaisePropertyChanged("NetworkList1");
This might be preventing your view from updating properly.
In addition to Gaurav answer, if _networkList1 is a private field in your NetworkViewModel class, how is it possible to get access to it in Networking window? I mean what's the meaning of the following line?
lb1.ItemsSource = _networkList1;
when you define a Property (NetworkList1), you have to use it in order to get advantages of its features (e.g. to get RaisePropertyChanged working). Otherwise what's the point, you could have just defined a field (_networklist1). So changing
_networkList1 = new ObservableCollection<Person>()
to
NetworkList1 = new ObservableCollection<Person>()
results in actually setting NetworkList1 and therefore RaisePropertyChanged("NetworkList1") to be fired. (however if you want to just show data in a your listbox this is unnecessary)
and if i'm getting it right, changing this:
public partial class Networking : Window
{
public Networking()
{
InitializeComponent();
this.DataContext = new NetworkViewModel();
lb1.ItemsSource = _networkList1;
}
}
to
public partial class Networking : Window
{
public NetworkViewModel MyViewModel { get; set; }
public Networking()
{
InitializeComponent();
MyViewModel = new NetworkViewModel();
this.DataContext = MyViewModel;
}
}
should get your binding to work.
*Note that when you set DataContext to NetworkViewModel, then the binding in
<ListBox x:Name="lb1" HorizontalAlignment="Left" ItemsSource="{Binding NetworkList1}">
works, because NetworkList1 is a Property of NetworkViewModel.
Do not call RaisePropertyChanged() method on ObservableCollection<T>, for god's sake. This is a common mistake in a majority of cases (however, there are cases, where you need to reset ObservableCollection<T> using new keyword, but they are kinda rare).
This is a special type of collection which notifies UI internally about all the changes of its content (like add, remove etc.). What you need is to set the collection using new keyword once in a lifetime of your ViewModel, and then manipulate your items via Add(T item), Remove(T item), Clear() methods etc.
and UI will get notified about it and updated automatically.
A bit silly question, but somehow I can't find how to bind the DataContext of the Window or its Content (e.g a Grid panel) to one specific property of the Window (say, ViewModel in my example below):
Code:
internal partial class MyWin : Window
{
public MyViewModelType ViewModel { get; set; }
...
}
XAML:
<Window x:Class="MyNs.MyWin"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}" />
<Grid DataContext={Binding ViewModel}> <!-- doesn't work??? -->
...
</Grid>
</Window>
I think you have this the wrong way around
if your window does the hooking up, it will work okay
public partial class MyWindow
{
public MyWindow()
{
InitializeComponent();
DataContext = ViewModel = new MyViewModelType();
}
}
Please define field for the viewmodel as it is not changing and implement INPC
private MyViewModelType viewmodel;
public MyViewModelType ViewModel
{
get
{
if(viewmodel == null)
{
viewmodel = new MyViewModelType();
}
return viewmodel;
}
set
{
viewmodel = value;
OnPropertyChanged("ViewModel")
}
}
Rest of code remains the same.
I'm trying to have a MainWindow that is bound to the a view. I change that view in code and expect it to update in the Main Window, however that is not happening.
I have this code in my XAML
<Grid>
<ContentControl Content="{Binding Source={StaticResource ViewModelLocator}, Path=MainWindowViewModel.CurrentControl}" />
</Grid>
I then change my Control via this code
public class MainWindowViewModel : ReactiveObject
{
private UserControl _CurrentControl = null;
public UserControl CurrentControl
{
get
{
if (_CurrentControl == null)
{
_CurrentControl = new HomePage();
}
return _CurrentControl;
}
set
{
this.RaiseAndSetIfChanged(x => x.CurrentControl, value);
}
}
}
As you can see I'm using the ReactiveUI library.
Is ContentControl the wrong thing to use in that view or am I just not binding and updating correctly?
There is actually a far better way to do this, using ViewModelViewHost:
<Grid DataContext="{Binding ViewModel, ElementName=TheUserControl}">
<ViewModelViewHost ViewModel="{Binding CurrentControlViewModel}" />
</Grid>
Now, your class will look something like:
public class MainWindowViewModel : ReactiveObject
{
private ReactiveObject _CurrentControlViewModel = new HomePageViewModel();
public ReactiveObject CurrentControlViewModel {
get { return _CurrentControl; }
set { this.RaiseAndSetIfChanged(x => x.CurrentControlViewModel, value); }
}
}
And somewhere in your app's startup, you should write:
RxApp.Register(typeof(IViewFor<HomePageViewModel>), typeof(HomePage));
What's ViewModelViewHost?
ViewModelViewHost will take a ViewModel object that you provide via Bindings, and look up a View that fits it, using Service Location. The Register call is how you can associate Views with ViewModels.
why you call your class MainWindowViewModel? when you wanna do mvvm you shouldn't have properties with type UserControl in your VM.
the usual mvvm way looks like this:
viewmodel with INotifyPropertyChanged
public class MyViewmodel
{
public IWorkspace MyContent {get;set;}
}
xaml content control with binding to your VM
<ContentControl Content="{Binding MyContent}"/>
datatemplate --> so that wpf knows how to render your IWorkspace
<DataTemplate DataType="{x:Type local:MyIWorkSpaceImplementationType}" >
<view:MyWorkspaceView />
</DataTemplate>
I think you have several muddled concepts here and they are getting in each others way.
Firstly you aren't actually using ANY of the reactiveUI code, it never gets called. Since your get accessor implements a lazy instantiation pattern then it means the set accessor is ignored. This means that the view never notifies the property change, so you never get updates.
I'd recommend using something more like
private UserControl _currentControl;
public MainWindowVirwModel()
{
CurrentControl = new HomePage();
}
public UserControl CurrentControl
{
get { return _curentControl;}
set { this.RaiseAndSetIfChanged(...); }
}
In addition, this still mixes up View components i.e. HomePage, inside your ViewModel tier which will making unit testing far more difficult.