Description:
I have some View having its DataContext is already set to some List.
I also have in there a ComboBox that should trigger a Visibility event to a StackPanel. It is done through a property "SelectedVisibility" that is implementing a INotifyPropertyChanged.
Issue:
The property "SelectedVisibility" isn't part of the DataContext but in a ViewModel class and I cannot find any way to explicitely bind my ViewModel to reach that property.
Question:
Would you know how I could explicitely define my VM to be the DataContext of the SelectedValue binding in my ComboBox?
Code details:
View XAML:
<ComboBox ItemsSource="{Binding Source={StaticResource VisibilityEnum}}" SelectedValue="{Binding Path=SelectedVisibility}"/>
<StackPanel Visibility="{Binding Path=SelectedVisibility,Converter={StaticResource SelectedValueToVisible}}">
View Code behind:
public Counterparties_UserInputs()
{
// Cannot bind this as already bound
// this.DataContext = _VM;
InitializeComponent();
}
View Model:
public event PropertyChangedEventHandler PropertyChanged;
public string SelectedVisibility
{
get
{
return _selectedVisibility;
}
set
{
_selectedVisibility= value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedVisibility"));
}
}
}
Thank you in advance!
You can add a new dependancy property to your view, bind your view model to this property and then use this property as DataContext for your StackPanel and ComboBox. For example ("this" is the name of your view, "AdditionalContext" is the dependancy property you declare to store your viewmodel):
<StackPanel DataContext="{Binding AdditionalContext, ElementName=this}" Visibility="{Binding Path=SelectedVisibility, Converter={StaticResource SelectedValueToVisible}}"/>
However you should not do it, since it is a violation of MVVM pattern. The whole point of viewmodel is that you use it as DataContext for your view. The correct approach to your issue is to move List declaration to your viewmodel.
Related
I have a WPF project with a view model and some nested UI elements. Here is the (relevant section of) XAML:
<UserControl> // DataContext is MyVM (set programmatically)
<TreeView ItemsSource="{Binding Trees}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Subtrees}">
<StackPanel>
<ListView ItemsSource="{Binding Contents}"
SelectedValue="{Binding SelectedContent}" // won't work: Tree has no such property
SelectionMode="Single"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</UserControl>
Here the code for the ViewModel class:
public class MyVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public IEnumerable<Tree> Trees { get; set; }
private object _selectedContent;
public string SelectedContent
{
get => _selectedContent;
set
{
_selectedContent = value;
OnPropertyChanged();
}
}
}
Here the code for class Tree:
public class Tree
{
public IEnumerable<Tree> Subtrees { get; set; }
public IEnumerable<string> Contents { get; set; }
}
I want to allow only one selection globally for all ListViews. Just like here, I want to bind all ListViews to the property SelectedContent in the view model MyVM.
The problem is that the data context of the ListView is a Tree, and not the MyVM from the top user control. (It should be Tree, since we want to show the Contents.) I know I can bind downwards using SelectedValuePath, but how do I go up instead in order to bind SelectedValue to the MyVM property SelectedContent?
I tried SelectedValue="{Binding RelativeSource={RelativeSource AncestorType ={x:Type UserControl}}, Path=SelectedContent}", but it did not work.
There is a comment here, saying:
Just wanted to note here that if you want to bind to a property in the
DataContext of the RelativeSource then you must explicitly specify it:
{Binding Path=DataContext.SomeProperty, RelativeSource=.... This was
somewhat unexpected for me as a newbie when I was trying to bind to a
parent's DataContext within a DataTemplate.
This comment deserves more attention, so I'll use it as the correct answer.
Try to use
SelectedValue="{Binding RelativeSource={RelativeSource
Mode=FindAncestorBindingContext, AncestorType ={x:Type MyVM}},
Path=SelectedContent}"
Maybe, you'll need to describe your model's namespace in header:
xmlns:viewmodel="clr-namespace:_your_namespace_.ViewModels"
and use
SelectedValue="{Binding RelativeSource={RelativeSource
Mode=FindAncestorBindingContext, AncestorType ={x:Type viewmodel:MyVM}},
Path=SelectedContent}"
I created UserControl with viewmodel. It has DependencyProperty which only works if the value is passed directly. If the value is passed through the binding, it no longer works.
Here is the view code:
This is a closed element not associated with any other. All listed items belong to him. This is a code shortening, I am not going to present whole, immeasurable structures.
View
public partial class SomeView : UserControl
{
public SomeView()
{
InitializeComponent();
SetBinding(ActiveProperty, new Binding(nameof(SomeViewModel.Active)) { Mode = BindingMode.OneWayToSource });
}
#region ActiveProperty
public static readonly DependencyProperty ActiveProperty = DependencyProperty.Register(nameof(Active), typeof(bool), typeof(VNCBoxView));
public bool Active
{
get { return (bool)GetValue(ActiveProperty); }
set { SetValue(ActiveProperty, value); }
}
}
VievModel
public class SomeViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool active;
public bool Active
{
get { return active; }
set
{
active = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Active)));
}
}
}
UserControl
<UserControl ...>
<UserControl.DataContext>
<viewModels:SomeViewModel />
</UserControl.DataContext>
<Grid>
<TextBlock Text="{Binding Active}" />
</Grid>
</UserControl>
===================================================
When working with a ready component, which is an individual, separate entity, the problem occurs depending on how it is used.
I remind you that the elements used in the view in question are a closed whole that does not connect with the element in which it is used. It is the transfer of value that is the matter of the problem.
This is working usage:
<local:SomeView Active="True" />
In viewmodel, the setter is invoked twice, once with false and then with true.
If the value comes from binding, it doesn't work:
<local:SomeView Active="{Binding SomeParentProperty}" />
In viewmodel, setter is only called once with the value false.
Setters in a view are never called, in both cases.
Please help
There is no IsConnected property in the SomeViewModel instance in the current DataContext of the UserControl, hence the Binding
<local:SomeView Active="{Binding IsConnected}" />
won't work. It tries to resolve the PropertyPath against the current DataContext, unless you explicitly specify its Source, RelativeSource or ElementName.
This is the exact reason why UserControls should never explicitly set their own DataContext, and hence never have something like an own, private view model.
The elements in the UserControl's XAML would not bind to properties of such a private view model object, but directly to the properties of the UserControl, for example like
<TextBlock Text="{Binding Active,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
When you set the DataContext explicitly in the UserControl like this:
<UserControl.DataContext>
<viewModels:SomeViewModel />
</UserControl.DataContext>
...you can no longer bind to SomeView's DataContext in the consuming view like this:
<local:SomeView Active="{Binding IsConnected}" />
...because SomeViewModel doesn't have any IsConnected property.
You should avoid setting the DataContext explicitly and let the UserControl inherit its DataContext from its parent element. You can still bind to the dependency property of the UserControl itself using a RelativeSource or an ElementName:
<UserControl ...>
<Grid>
<TextBlock Text="{Binding Active, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</Grid>
</UserControl>
Besides, SomeViewModel seems superfluous in your example since the UserControl already has an Active property.
first attempt at MVVM and WPF (steep learning curve).
In my ViewModel I want to run the following code to add a "layoutDocument" which is an AvalonDock layout into my Mainform UI.
ViewModel class:
LayoutDocument layoutDocument = new LayoutDocument { Title = "Plan Layout" };
Window mainWindow = Application.Current.Windows.OfType<Window>().Where(x => x.Name == "MainWindow").FirstOrDefault();
if (mainWindow != null)
{
mainWindow.mainPanel.Children.Add(layoutDocument);
}
The above code gives me the following error:
"'Window' does not contain definition for 'mainPanel' and no extension method for 'mainPanel'".
Note in my XAML below that "LayoutDocumentPane" does contain a name "mainPanel".
I have tried adding the above code directly into my MainForm View Class (excluding the Application.Current.Windows.OfType and If statement bit) and just including the:
mainPanel.Children.Add(layoutDocument);
And it works fine (a new layout is created in my MainForm when I click the button).
However, as I want to stickto MVVM this is not a suitable solution.
How can I add "layoutDocument" to MainWindow from ViewModel? Thanks in advance.
An extract of my XAML looks like this:
<Window x:Class="LiveExplorer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LiveExplorer"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:vm="clr-namespace:WpfApp1.ViewModel">
<Grid> etc etc etc here---
<xcad:LayoutDocumentPaneGroup>
<xcad:LayoutDocumentPane x:Name="mainPanel">
<xcad:LayoutDocument ContentId="document1" Title="Document 1" >
<Button Content="Document 1 Content" HorizontalAlignment="Center" VerticalAlignment="Center"
Command="{Binding NewPlanCommand, Source={StaticResource viewModel}}"
/>
</xcad:LayoutDocument>
<xcad:LayoutDocument ContentId="document2" Title="Document 2">
<TextBox Text="Document 2 Content" AcceptsReturn="True"/>
</xcad:LayoutDocument>
</xcad:LayoutDocumentPane>
</xcad:LayoutDocumentPaneGroup >
EDIT:
Whilst the accepted answer does not answer the question in terms of MMVM, it does correct the coding error.
What you've tried to implement does not follow the MVVM pattern. You need to take care of 3 things to get started:
ViewModels
Initialize the ViewModel binded to the window
Binding ViewModel to the UI in XAML
ViewModels:
Create a viewmodel that will be binded to your MainWindow and create an observable collection inside that MainWindowViewModel that contains a list of object that will contain data that can be used in the UI:
public ObservableCollection<LayoutDocumentViewModel> LayoutDocument {get;set;}
Make sure that both the MainWindowViewModel and the LayoutDocumentViewModel inherits from INotifyPropertyChanged(Implement Property Change Notification) or if you use MVVMLight (or similar) from ViewModelBase.
The LayoutDocumentViewModel is just a ViewModel that will be used to store information about your layout document and that can be binded to the UI.
public LayoutDocumentViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName]
string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Name");
}
}
}
I would strongly recommend that you use MVVMLight (or similar) or put the INotifyPropertyChange code into a base class i.e. ViewModelBase for example.
For simplicity sake in this example, I'm initializing the observable collection and creating a couple of document layouts objects directly in the MainWindowViewModel but you'll need to research this further and find out where it is appropriate for you to initialize and/or create these.
public MainPageViewModel()
{
DocumentLayouts = new ObservableCollection();
DocumentLayouts.Add(new DocumentLayout {Name="Layout1"});
DocumentLayouts.Add(new DocumentLayout {Name="Layout2"});
}
The above takes care of creating your MainWindowViewModel and layout documents.
Initializing MainViewModel (and binded to the MainWindow.xaml). Note this is a quick and dirty way to get you started and you should really look into IoC containers.
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
Finally, bind your ViewModel & UI
XAML:
<Grid>
<ItemsControl ItemsSource="{Binding LayoutDocuments}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Note: Just replace the Label by your LayoutDocument control and bind it to the relevant element properties you have declared in LayoutDocumentViewModel.
Hope this helps get you started.
This is not related to MVVM but to be able access the mainPanel you need to cast the returned Window to a MainWindow:
MainWindow mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
if (mainWindow != null)
{
mainWindow.mainPanel.Children.Add(layoutDocument);
}
A view model shouldn't access any window directly though. This breaks the MVVM pattern.
Does anybody know how I can synchronize my properties, which are in a ViewModel, with my Dependency Properties, which are in the View?
I am trying to make a UserControl, which will then be hosted by a WPF-Window (MainWindow.xaml). The UserControl has an own ViewModel which contains ICommands and properties.
The problem is, that I also have to return certain properties to the MainWindow(.xaml) and also set them.
Currently my classes are looking like that:
MainWindow.xaml
<TextBox Name="tbInput" VerticalAlignment="Top" HorizontalAlignment="Stretch" Grid.Row="0"></TextBox>
<local:View x:Name="appEntryView" Pfad="{Binding ElementName=tbInput, Path=Text}" Grid.Row="1" Margin="10"/>
View.xaml
<UserControl x:Class="DependencyProperties.Test"
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:DependencyProperties_Intro"
x:Name="obj"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding ElementName=obj, Path=Pfad}"/>
</Grid>
View.xaml.cs
public partial class View: UserControl, INotifyPropertyChanged
{
public String Pfad
{
get { return (String)GetValue(PfadProperty); }
set { SetValue(PfadProperty, value); OnNotifyPropertyChanged("Pfad"); }
}
// Using a DependencyProperty as the backing store for Path. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PfadProperty =
DependencyProperty.Register("Pfad", typeof(String), typeof(GraphSharpTest), new PropertyMetadata(default(string)));
public View()
{
InitializeComponent();
this.DataContext = new ViewModel();
var name = "Pfad";
var binding = new Binding(name) { Mode = BindingMode.TwoWay };
this.SetBinding(PfadProperty, binding);
}
}
ViewModel.cs
public class ViewModel: INotifyPropertyChanged
{
private String m_Pfad;
public String Pfad
{
get { return m_Pfad; }
set { m_Pfad = value; OnNotifyPropertyChanged("Pfad"); }
}
public void OnNotifyPropertyChanged(String info)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(info));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The dependency property works fine, but the setter method of "Pfad" in the ViewModel never gets called at all.
Thanks in advance!
Raising PropertyChanged in the CLR properties of dependency properties is a common mistake. You should not place any code there as it is not used by bindings at all. They exist merely for setting the property once in code or XAML, thus you also will not hit any breakpoints you set there.
var name = "Pfad";
var binding = new Binding(name) { Mode = BindingMode.TwoWay };
this.SetBinding(PfadProperty, binding);
I take it you want to forward the value to your view-model. This is not going to work as you can only bind the property once. Right now you also bind the property here:
<local:View x:Name="appEntryView" Pfad="{Binding ElementName=tbInput, Path=Text}" Grid.Row="1" Margin="10"/>
You could subscribe to the dependency property changes using the respective meta data when registering it, providing a callback, there you could set the value in the view-model.
The thing is: The view-model is private to the View, there really is no point in doing this synchronization if no-one has access to the data. You probably want the property to be either settable from the outside, treating the UserControl more like a control, discarding the view-model, or you want the view-model to be passed from outside as DataContext, and the view binds directly to it.
You need to be careful with explicitly setting the DataContext of UserControls in their definition, as it can obfuscate what is happening and lead to bindings unexpectedly breaking. If you want to set properties on the UserControl instance i would recommend avoiding it.
I like to create a UserControl with own Header Property.
public partial class SomeClass: UserControl, INotifyPropertyChanged
{
public SomeClass()
{
InitializeComponent();
}
private string header;
public string Header
{
get { return header; }
set
{
header = value;
OnPropertyChanged("Header");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
in UserContol xaml:
Label Name="lbHeader" Grid.Column="0" Content="{Binding Path=Header}"
If I set the value: AA2P.Header = "SomeHeeaderText"; than the label.Caption will not changed. How can I solve that problem?
In Windows xaml:
uc:SomeClass x:Name="AA2P"
If I give directly a value to label (lbHeader.Content = header;) instead of OnPropertyChanged("Header"); its work but, why it does not work with OnPropertyChanged?
I need to use DataContext for somethig else. I try to use dependency property but something is wrong.
public partial class tester : UserControl
{
public tester()
{
InitializeComponent();
}
public string Header
{
get { return (string)GetValue(MyDependencyProperty); }
set { SetValue(MyDependencyProperty, value); }
}
public static readonly DependencyProperty MyDependencyProperty =
DependencyProperty.Register("MyDependencyProperty", typeof(string), typeof(string));
}
<UserControl ... x:Name="mainControl">
<TextBlock Text="{Binding ElementName=mainControl, Path=MyDependencyProperty}"/>
</UserControl>
<Window ...>
<my:tester Header="SomeText" />
</Window>
It does not work. What I do wrong?
Thanks!
The easiest approach is to just the DataContext of your object. One way of doing that is directly in the constructor like this:
public SomeClass()
{
InitializeComponent();
DataContext = this;
}
Setting the DataContext will specify where new data should be fetched from. There are some great tips and information in the article called WPF Basic Data Binding FAQ. Read it to better understand what the DataContex can be used for. It is an essential component in WPF/C#.
Update due to update of the question.
To my understanding you should change the first argument of DependencyProperty.Register to the name of the property that you want to bind to, here "Header" as well as the second argument to the type of your class, here SomeClass. That would leave you with:
public static readonly DependencyProperty MyDependencyProperty =
DependencyProperty.Register("Header", typeof(SomeClass), typeof(string));
But i seldom use dependency properties so I am not positive that this is it, but its worth a try..
If you need the Data context for something else. You can also utilize the ElementName property in the Binding.
<UserControl
x:Class="MyControl.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="mainControl">
<TextBlock Text="Binding ElementName=mainControl, Path=MyDependencyProperty}"/>
</UserControl>
[Edit]
I should add something. Make the "Header" property a dependency property, this will make your live much easier. In UI Controls you should make property almost always a dependency property, every designer or user of your control will thank you.
The UserControl itself needs the DataContext of where it is used later. But the controls inside the UserControl need the UserControl as their DataContext, otherwise they also will inherit the DataContext from the later usage context. The trick is to set the DataContext of the UserControl's child to that of the UserControl, so it now can use the dependency properties of the UserControl.
<UserControl x:Class="MyControl.MyUserControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl,AncestorLevel=1}}">...</Grid>
</UserControl>
If you do this this way the children of the Grid can have simple {Binding dp's name} without additionally ElementName parameters.