WPF Navigation while Keeping Menubar (Header) and Footer Fixed - c#

We used to develop application with WinForms and nowadays we are trying to migrate it to WPF, starting from zero. In our application we have 3 main parts on screen which are Header (all main menu items), body (based on MDI container, content can be changed) and the footer (where general status is displayed, logo etc.) Whenever a user clicks on different menuitem from header part, the body part would change it's children to that Panel/Form.
There are lot's of good examples/tutorials on the Internet but I am confused about how to achieve to create a navigation service that allows to switch the view of body part.
Any suggestions would be appriciated, thanks in advance.

There are indeed multiple ways to archive this result. I will try and explain the very basic/easiest way to get the result.
While this will not provide example in combination with Menu Control, I think it will help you to understand the concept
In your MainWindow you can split use Grid layout and split the space into 3 parts as you wanted. Your Main window Xaml should look something like this :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<ContentControl x:Name="Header"/>
<ContentControl x:Name="Content" Grid.Row="1/>
<ContentControl x:Name="Footer" Grid.Row="2"/>
</Grid>
In your content control you can insert your "UserControls" for the Header,Content,Footer. Now to the navigation part:
As mentioned there are many ways to archive this and I will describe what I do consider the easiest way (not the most flexible way however, so keep that in mind).
First I will suggest to make a navigation Model:
public class NavigationModel
{
public NavigationModel(string title, string description, Brush color)
{
Title = title;
Description = description;
Color = color;
}
public string Title { get; set; }
public string Description { get; set; }
public Brush Color { get; set; }
public override bool Equals(object obj)
{
return obj is NavigationModel model &&
Title == model.Title &&
Description == model.Description &&
Color == model.Color;
}
public override int GetHashCode()
{
return HashCode.Combine(Title, Description, Color);
}
}
We create a new class that will handle the navigation collection, lets call it navigation service.
public class NavigationService
{
public List<NavigationModel> NavigationOptions { get=>NavigationNameToUserControl.Keys.ToList(); }
public UserControl NavigateToModel(NavigationModel _navigationModel)
{
if (_navigationModel is null)
//Or throw exception
return null;
if (NavigationNameToUserControl.ContainsKey(_navigationModel))
{
return NavigationNameToUserControl[_navigationModel].Invoke();
}
//Ideally you should throw here Custom Exception
return null;
}
//Usage of the Func, provides each call new initialization of the view
//If you need initialized views, just remove the Func
//-------------------------------------------------------------------
//Readonly is used only for performance reasons
//Of course there is option to add the elements to the collection, if dynamic navigation mutation is needed
private readonly Dictionary<NavigationModel, Func<UserControl>> NavigationNameToUserControl = new Dictionary<NavigationModel, Func<UserControl>>
{
{ new NavigationModel("Navigate To A","This will navigate to the A View",Brushes.Aqua), ()=>{ return new View.ViewA(); } },
{ new NavigationModel("Navigate To B","This will navigate to the B View",Brushes.GreenYellow), ()=>{ return new View.ViewB(); } }
};
#region SingletonThreadSafe
private static readonly object Instancelock = new object();
private static NavigationService instance = null;
public static NavigationService GetInstance
{
get
{
if (instance == null)
{
lock (Instancelock)
{
if (instance == null)
{
instance = new NavigationService();
}
}
}
return instance;
}
}
#endregion
}
This service will provide us with action to receive desired UserControll (note that I am using UserControl instead of pages, since they provide more flexibility).
Not we create additional Converter, which we will bind into the xaml:
public class NavigationConverter : MarkupExtension, IValueConverter
{
private static NavigationConverter _converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter is null)
{
_converter = new NavigationConverter();
}
return _converter;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
NavigationModel navigateTo = (NavigationModel)value;
NavigationService navigation = NavigationService.GetInstance;
if (navigateTo is null)
return null;
return navigation.NavigateToModel(navigateTo);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> null;
}
In our MainWindows.xaml add reference to the Converter namespace over xmlns, for example :
xmlns:Converter="clr-namespace:SimpleNavigation.Converter"
and create insance of converter :
<Window.Resources>
<Converter:NavigationConverter x:Key="NavigationConverter"/>
</Window.Resources>
Note that your project name will have different namespace
And set the Add datacontext to the instance of our Navigation Service:
You can do it over MainWindow.Xaml.CS or create a ViewModel if you are using MVVM
MainWindow.Xaml.CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Service.NavigationService.GetInstance.NavigationOptions;
}
}
Now all is left to do is navigate. I do not know how about your UX, so I will just provide example from my github of the MainWindow.xaml. Hope you will manage to make the best of it :
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<ListView
x:Name="NavigationList"
ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<Border
Height="35"
BorderBrush="Gray"
Background="{Binding Color}"
ToolTip="{Binding Description}"
BorderThickness="2">
<TextBlock
VerticalAlignment="Center"
FontWeight="DemiBold"
Margin="10"
Text="{Binding Title}" />
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentControl
Grid.Column="1"
Content="{Binding ElementName=NavigationList,Path=SelectedItem,Converter={StaticResource NavigationConverter}}"/>
</Grid>
Just in case I will leave you a link to github, so it will be easier for you
https://github.com/6demon89/Tutorials/blob/master/SimpleNavigation/MainWindow.xaml
Using same principle to use Menu Navigation
<Window.DataContext>
<VM:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Converter:NavigationConverter x:Key="NavigationConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="Navigaiton"
ItemsSource="{Binding NavigationOptions}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem
Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Header="{Binding Title}"
Background="{Binding Color}"
ToolTip="{Binding Description}">
</MenuItem>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
<ContentControl
Grid.Row="1"
Background="Red"
BorderBrush="Gray"
BorderThickness="2"
Content="{Binding CurrentView,Converter={StaticResource NavigationConverter}}"/>
<Border Grid.Row="2" Background="{Binding CurrentView.Color}">
<TextBlock Text="{Binding CurrentView.Description}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</Grid>
And we have in VM List of the navigation Models, Current Model and the navigation command :
public class MainViewModel:INotifyPropertyChanged
{
public List<NavigationModel> NavigationOptions { get => NavigationService.GetInstance.NavigationOptions; }
private NavigationModel currentView;
public NavigationModel CurrentView
{
get { return currentView; }
set
{
currentView = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CurrentView"));
}
}
RelayCommand _saveCommand;
public event PropertyChangedEventHandler PropertyChanged;
public ICommand NavigateCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(Navigate);
}
return _saveCommand;
}
}
private void Navigate(object param)
{
if(param is NavigationModel nav)
{
CurrentView = nav;
}
}
}
Sorry for long reply

I think that you not necessary have to start from scratch. You may have a look:
https://qube7.com/guides/navigation.html

Related

I can't bind class properties into text boxes (MVVM, WPF, C#)

I'm learning MVVM pattern and I have a simple issue.
I have created a class whose properties I want to bind into text boxes inside a Dialog (in this case I only have one property):
public class NewObjectClass : ObservableObject
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
This is the ViewModel of the Dialog:
class ObjCreationViewModel : ObservableObject
{
// Class whose properties I want to bind in text boxes
private NewObjectClass _newObject;
public NewObjectClass NewObject
{
get { return _newObject; }
set { _newObject = value;
OnPropertyChanged("NewObject");
}
}
// String to test binding
private string _test;
public string Test
{
get { return _test; }
set { _test = value; }
}
// Create new object view
private DelegateCommand _createNewObject;
public DelegateCommand CreateNewObjectCmd => _createNewObject ?? (_createNewObject = new DelegateCommand(CreateNewObject));
void CreateNewObject()
{
// Do things in the future
}
public ObjCreationViewModel()
{
// Instance of New Object
NewObjectClass NewObject = new NewObjectClass();
// New object property that I want to show in TextBox
NewObject.Name = "Why this one is not working?";
// String text that works
Test = "Why this is working?";
}
}
And finally this is the view:
<UserControl x:Class="AOE.MVVM.View.ObjCreationView"
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:AOE.MVVM.View"
xmlns:viewmodel="clr-namespace:AOE.MVVM.ViewModel"
mc:Ignorable="d"
MinHeight="500" MinWidth="500">
<UserControl.DataContext>
<viewmodel:ObjCreationViewModel/>
</UserControl.DataContext>
<Border Grid.ColumnSpan="4" CornerRadius="20"
Background="White" BorderBrush="Black" BorderThickness="1.5">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1">
<StackPanel Orientation="Horizontal">
<Label Margin="10" Content="Object name:" Style="{StaticResource DescriptionLabelStyle}"/>
<TextBox x:Name="TextObjectName" Width="200" Height="25" Text="{Binding NewObject.Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</StackPanel>
</StackPanel>
<Button Grid.Row="1" Margin="5" Name="CreateBtn" Width="75" Height="30" Content="Create" FontFamily="Verdana" Foreground="#007BC0" FontWeight="Bold" Background="Transparent" BorderThickness="3" BorderBrush="#007BC0" VerticalAlignment="Bottom" Command="{Binding CreateNewObjectCmd}"/>
</Grid>
</Border>
The issue is that I can't bind the property Name of "NewObject" into the text box, however if I bind "Test", It works properly.
I want to show in the textbox the property value and also if I change the value in the textbox, change the property value of the instance "NewObject".
Anybody could tell me what is wrong?
PD "ObservableObject class that implements PropertyChange event":
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
The line
NewObjectClass NewObject = new NewObjectClass();
defines a local variable NewObject, but does not set the property NewObject.
Change it to
NewObject = new NewObjectClass();

Preventing a BindingExpression Error on a property that does not always exist [duplicate]

I have a WPF window that uses multiple viewmodel objects as its DataContext. The window has a control that binds to a property that exists only in some of the viewmodel objects. How can I bind to the property if it exists (and only if it exists).
I am aware of the following question/answer: MVVM - hiding a control when bound property is not present. This works, but gives me a warning. Can it be done without the warning?
Thanks!
Some example code:
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="1" Name="ListView" Margin="25,0,25,0" ItemsSource="{Binding Path=Lst}"
HorizontalContentAlignment="Center" SelectionChanged="Lst_SelectionChanged">
</ListBox>
<local:SubControl Grid.Row="3" x:Name="subControl" DataContext="{Binding Path=SelectedVM}"/>
</Grid>
SubControl Xaml:
<UserControl x:Class="WpfApplication1.SubControl"
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:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="3" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffSometimes}"/>
<CheckBox IsChecked="{Binding Path=Sometimes}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
MainWindow Code Behind:
public partial class MainWindow : Window
{
ViewModel1 vm1;
ViewModel2 vm2;
MainViewModel mvm;
public MainWindow()
{
InitializeComponent();
vm1 = new ViewModel1();
vm2 = new ViewModel2();
mvm = new MainViewModel();
mvm.SelectedVM = vm1;
DataContext = mvm;
}
private void Lst_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox lstBx = sender as ListBox;
if (lstBx != null)
{
if (lstBx.SelectedItem.Equals("VM 1"))
mvm.SelectedVM = vm1;
else if (lstBx.SelectedItem.Equals("VM 2"))
mvm.SelectedVM = vm2;
}
}
}
MainViewModel (DataContext of MainWindow):
public class MainViewModel : INotifyPropertyChanged
{
ObservableCollection<string> lst;
ViewModelBase selectedVM;
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
Lst = new ObservableCollection<string>();
Lst.Add("VM 1");
Lst.Add("VM 2");
}
public ObservableCollection<string> Lst
{
get { return lst; }
set
{
lst = value;
OnPropertyChanged("Lst");
}
}
public ViewModelBase SelectedVM
{
get { return selectedVM; }
set
{
if (selectedVM != value)
{
selectedVM = value;
OnPropertyChanged("SelectedVM");
}
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ViewModel1 (with sometimes property):
public class ViewModel1 : ViewModelBase, INotifyPropertyChanged
{
private bool _always;
private string _onOffAlways;
private bool _sometimes;
private string _onOffSometimes;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel1()
{
_always = false;
_onOffAlways = "Always Off";
_sometimes = false;
_onOffSometimes = "Sometimes Off";
}
public bool Always
{
get { return _always; }
set
{
_always = value;
if (_always)
OnOffAlways = "Always On";
else
OnOffAlways = "Always Off";
OnPropertyChanged("Always");
}
}
public string OnOffAlways
{
get { return _onOffAlways; }
set
{
_onOffAlways = value;
OnPropertyChanged("OnOffAlways");
}
}
public bool Sometimes
{
get { return _sometimes; }
set
{
_sometimes = value;
if (_sometimes)
OnOffSometimes = "Sometimes On";
else
OnOffSometimes = "Sometimes Off";
OnPropertyChanged("Sometimes");
}
}
public string OnOffSometimes
{
get { return _onOffSometimes; }
set
{
_onOffSometimes = value;
OnPropertyChanged("OnOffSometimes");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ViewModel2 (without Sometimes property):
public class ViewModel2 : ViewModelBase, INotifyPropertyChanged
{
private bool _always;
private string _onOffAlways;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel2()
{
_always = false;
_onOffAlways = "Always Off";
}
public bool Always
{
get { return _always; }
set
{
_always = value;
if (_always)
OnOffAlways = "Always On";
else
OnOffAlways = "Always Off";
OnPropertyChanged("Always");
}
}
public string OnOffAlways
{
get { return _onOffAlways; }
set
{
_onOffAlways = value;
OnPropertyChanged("OnOffAlways");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class AlwaysVisibleConverter : IValueConverter
{
#region Implementation of IValueConverter
public object Convert(object value,
Type targetType, object parameter, CultureInfo culture)
{
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
There are many different ways one could approach your scenario. For what it's worth, the solution you already have seems reasonable to me. The warning you get (I presume you are talking about the error message output to the debug console) is reasonably harmless. It does imply a potential performance issue, as it indicates WPF is recovering from an unexpected condition. But I would expect the cost to be incurred only when the view model changes, which should not be frequent enough to matter.
Another option, which is IMHO the preferred one, is to just use the usual WPF data templating features. That is, define a different template for each view model you expect, and then let WPF pick the right one according to the current view model. That would look something like this:
<UserControl x:Class="TestSO46736914MissingProperty.UserControl1"
x:ClassModifier="internal"
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:l="clr-namespace:TestSO46736914MissingProperty"
mc:Ignorable="d"
Content="{Binding}"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate DataType="{x:Type l:ViewModel1}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="3" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffSometimes}"/>
<CheckBox IsChecked="{Binding Path=Sometimes}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type l:ViewModel2}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
</DataTemplate>
</UserControl.Resources>
</UserControl>
I.e. just set the Content of your UserControl object to the view model object itself, so that the appropriate template is used to display the data in the control. The template for the view model object that doesn't have the property, doesn't reference that property and so no warning is generated.
Yet another option, which like the above also addresses your concern about the displayed warning, is to create a "shim" (a.k.a. "adapter") object that mediates between the unknown view model type and a consistent one the UserControl can use. For example:
class ViewModelWrapper : NotifyPropertyChangedBase
{
private readonly dynamic _viewModel;
public ViewModelWrapper(object viewModel)
{
_viewModel = viewModel;
HasSometimes = viewModel.GetType().GetProperty("Sometimes") != null;
_viewModel.PropertyChanged += (PropertyChangedEventHandler)_OnPropertyChanged;
}
private void _OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
_RaisePropertyChanged(e.PropertyName);
}
public bool Always
{
get { return _viewModel.Always; }
set { _viewModel.Always = value; }
}
public string OnOffAlways
{
get { return _viewModel.OnOffAlways; }
set { _viewModel.OnOffAlways = value; }
}
public bool Sometimes
{
get { return HasSometimes ? _viewModel.Sometimes : false; }
set { if (HasSometimes) _viewModel.Sometimes = value; }
}
public string OnOffSometimes
{
get { return HasSometimes ? _viewModel.OnOffSometimes : null; }
set { if (HasSometimes) _viewModel.OnOffSometimes = value; }
}
private bool _hasSometimes;
public bool HasSometimes
{
get { return _hasSometimes; }
private set { _UpdateField(ref _hasSometimes, value); }
}
}
This object uses the dynamic feature in C# to access the known property values, and uses reflection on construction to determine whether or not it should try to access the Sometimes (and related OnOffSometimes) property (accessing the property via the dynamic-typed variable when it doesn't exist would throw an exception).
It also implements the HasSometimes property so that the view can dynamically adjust itself accordingly. Finally, it also proxies the underlying PropertyChanged event, to go along with the delegated properties themselves.
To use this, a little bit of code-behind for the UserControl is needed:
partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public ViewModelWrapper ViewModelWrapper { get; private set; }
public UserControl1()
{
DataContextChanged += _OnDataContextChanged;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
private void _OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ViewModelWrapper = new ViewModelWrapper(DataContext);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ViewModelWrapper)));
}
}
With this, the XAML is mostly like what you originally had, but with a style applied to the optional StackPanel element that has a trigger to show or hide the element according to whether the property is present or not:
<UserControl x:Class="TestSO46736914MissingProperty.UserControl1"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="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:l="clr-namespace:TestSO46736914MissingProperty"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding ViewModelWrapper, RelativeSource={RelativeSource AncestorType=UserControl}}">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="3" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<StackPanel.Style>
<p:Style TargetType="StackPanel">
<p:Style.Triggers>
<DataTrigger Binding="{Binding HasSometimes}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</p:Style.Triggers>
</p:Style>
</StackPanel.Style>
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffSometimes}"/>
<CheckBox IsChecked="{Binding Path=Sometimes}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
</UserControl>
Note that the top-level Grid element's DataContext is set to the UserControl's ViewModelWrapper property, so that the contained elements use that object instead of the view model assigned by the parent code.
(You can ignore the p: XML namespace…that's there only because Stack Overflow's XAML formatting gets confused by <Style/> elements that use the default XML namespace.)
While I in general would prefer the template-based approach, as the idiomatic and inherently simpler one, this wrapper-based approach does have some advantages:
It can be used in situations where the UserControl object is declared in an assembly different from the one where the view model types are declared, and where the latter assembly cannot be referenced by the former.
It removes the redundancy that is required by the template-based approach. I.e. rather than having to copy/paste the shared elements of the templates, this approach uses a single XAML structure for the entire view, and shows or hides elements of that view as appropriate.
For completeness, here is the NotifyPropertyChangedBase class used by the ViewModelWrapper class above:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
_RaisePropertyChanged(propertyName);
}
protected void _RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
For what it's worth, I prefer this approach to re-implementing the INotifyPropertyChanged interface in each model object. The code is a lot simpler and easier to write, simpler to read, and less prone to errors.
Here's a fairly simple solution using DataTriggers and a custom converter:
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={HasPropertyConverter PropertyName=Sometimes}}" Value="True">
<Setter Property="IsChecked" Value="{Binding Sometimes}" />
</DataTrigger>
</Style.Triggers>
</Style>
The converter:
public class HasPropertyConverter : IValueConverter
{
public string PropertyName { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (string.IsNullOrWhiteSpace(PropertyName))
return DependencyProperty.UnsetValue;
return value?.GetType().GetProperty(PropertyName) != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
public class HasPropertyConverterExtension : MarkupExtension
{
public string PropertyName { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
=> new HasPropertyConverter { PropertyName = PropertyName };
}

Multiple ItemsControl on single collection applies filter to all views at once

Prerequisites: .NET 4.5.1
I have three TreeView controls that display three filtered variants of single collection instance. When I try to apply a filter on Items collection of one of controls this filter propagates to other controls automatically which prevents me to use different filters on different controls.
Is there any way to achieve the same result without having to maintain three instances of collections at once?
An example that shows the problem follows below. First two ListViews are bound to the same collection instance directly. Third one is bound to that instance through CompositeCollection. And the fourth is bound to independent collection. When I press "Set Filter" button ItemsControl.Items.Filter property if first ListView is set to IsAllowedItem method of WTest window. After this second istView.Items.Filter property somehow points to the same method while third and fourth ListView returns null. Another effect is that though third ListView shows null filter its collection is still filtered as you can see if you run the example. This very strange effect arises from the behavior of ItemCollection class that when based on ItemsSource property of owner element acquires underlying CollectionView from some application-wide storage via CollectionViewSource.GetDefaultCollectionView method. I don't know the reason of this implementation but suspect suspect that it's performance.
Test window WTest.xaml:
<Window x:Class="Local.WTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:local="clr-namespace:Local"
Name="_WTest" Title="WTest" Height="300" Width="600">
<Window.Resources>
<c:ArrayList x:Key="MyArray">
<s:String>Letter A</s:String>
<s:String>Letter B</s:String>
<s:String>Letter C</s:String>
</c:ArrayList>
<CompositeCollection x:Key="MyCollection" >
<CollectionContainer Collection="{StaticResource ResourceKey=MyArray}"/>
</CompositeCollection>
<c:ArrayList x:Key="AnotherArray">
<s:String>Letter A</s:String>
<s:String>Letter B</s:String>
<s:String>Letter C</s:String>
</c:ArrayList>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Name="FilterLabel1"/>
<TextBlock Grid.Row="0" Grid.Column="1" Name="FilterLabel2"/>
<TextBlock Grid.Row="0" Grid.Column="2" Name="FilterLabel3"/>
<TextBlock Grid.Row="0" Grid.Column="3" Name="FilterLabel4"/>
<ListView Grid.Row="1" Grid.Column="0" Name="View1" ItemsSource="{StaticResource ResourceKey=MyArray}"/>
<ListView Grid.Row="1" Grid.Column="1" Name="View2" ItemsSource="{StaticResource ResourceKey=MyArray}"/>
<ListView Grid.Row="1" Grid.Column="2" Name="View3" ItemsSource="{StaticResource ResourceKey=MyCollection}"/>
<ListView Grid.Row="1" Grid.Column="3" Name="View4" ItemsSource="{StaticResource ResourceKey=AnotherArray}"/>
<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" Content="Set Filter" Click="OnSetFilterButtonClick"/>
</Grid>
</Window>
Code behind WTest.xaml.cs
namespace Local
{
using System.Windows;
public partial class WTest : Window
{
public WTest()
{
InitializeComponent();
UpdateFilterLabels();
}
private bool IsAllowedItem(object item)
{
return "Letter A" == (string)item;
}
private void OnSetFilterButtonClick(object sender, RoutedEventArgs e)
{
View1.Items.Filter = IsAllowedItem;
UpdateFilterLabels();
}
private void UpdateFilterLabels()
{
FilterLabel1.Text = (null == View1.Items.Filter) ? "No Filter" : View1.Items.Filter.Method.Name;
FilterLabel2.Text = (null == View2.Items.Filter) ? "No Filter" : View2.Items.Filter.Method.Name;
FilterLabel3.Text = (null == View3.Items.Filter) ? "No Filter" : View3.Items.Filter.Method.Name;
FilterLabel4.Text = (null == View4.Items.Filter) ? "No Filter" : View4.Items.Filter.Method.Name;
}
}
}
And result after "Set Filter" button is clicked:
Example: result of clicking "Set Filter" button
Create CollectionViewSource as a Resource.
<CollectionViewSource x:Key="CVSKey" Source="{DynamicResource MyArray}"/>
Use this CollectionViewSource as your ItemsSource . Replace your View1 as :
<!--<ListView Grid.Row="1" Grid.Column="0" Name="View1" ItemsSource="{DynamicResource ResourceKey=MyArray}"/>-->
<ListView Grid.Row="1" Grid.Column="0" Name="View1" ItemsSource="{Binding Source={StaticResource ResourceKey=CVSKey}}"/>
Thats it, now everything will work as you want it to.
Additionally, now you can apply filtering to this CollectionViewSource instead of View1 :
((CollectionViewSource)this.Resources["CVSKey"]).Filter += List_Filter;
void List_Filter(object sender, FilterEventArgs e)
{
e.Accepted = (e.Item.ToString() == "Letter A") ? true : false;
}
Create separate CollectionViewSource for separate ListBoxes to create separate views from same underlying collection.
Search google for CollectionViewSource.
Change the OnSetFilterButtonClick Method as below
private void OnSetFilterButtonClick(object sender, RoutedEventArgs e)
{
//Create a new listview by the ItemsSource,Apply Filter to the new listview
ListCollectionView listView = new ListCollectionView(View1.ItemsSource as IList);
listView.Filter = IsAllowedItem;
View1.ItemsSource = listView;
UpdateFilterLabels();
}
I found a simple solution that does not require creating a CollectionViewSource resource in XAML or a ListCollectionView in code for every collection that needs its own filter.
My solution is to use an ValueConverter that converts the ItemsSource source to a CollectionViewSource.View
ItemsSourceConverter:
[ValueConversion(typeof(IEnumerable), typeof(IEnumerable))]
public class ItemsSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is IEnumerable itemsSource && itemsSource != null)
{
return new CollectionViewSource() { Source = itemsSource }.View;
}
else
{
throw new Exception($"Value must be an {nameof(IEnumerable)}");
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
XAML:
<Window ...>
<Window.Resources>
<local:ItemsSourceConverter x:Key="ItemsSourceConverter"/>
</Window.Resources>
...
<ItemsControl Name="View1",
ItemsSource="{Binding Collection1, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}" />
<ItemsControl Name="View2",
ItemsSource="{Binding Collection3, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}" />
<ItemsControl Name="View3",
ItemsSource="{Binding Collection3, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}" />
</Window>
Code behind:
public partial class MainWindow : Window
{
ObservableCollection<DataClass> Collection1 { get; private set; }
ObservableCollection<DataClass> Collection2 { get; private set; }
ObservableCollection<DataClass> Collection3 { get; private set; }
public MainWindow()
{
InitializeComponent();
}
...
private void SetFilters()
{
View1.Filter = (item) =>
{
// Filter logic
};
View2.Filter = (item) =>
{
// Filter logic
};
View2.Filter = (item) =>
{
// Filter logic
};
}
...
}
MVVM ItemsControl with Filter binding
If we want to use the above solution with MVVM, we can create an attached property to bind the ItemsControl.Filter to a filter defined in the ViewModel.
Filter attached property:
public static class CollectionViewExtensions
{
public static readonly DependencyProperty FilterProperty = DependencyProperty.RegisterAttached(
"Filter",
typeof(Predicate<object>),
typeof(CollectionViewExtensions),
new PropertyMetadata(default(Predicate<object>), OnFilterChanged));
public static void SetFilter(ItemsControl element, Predicate<object> value)
{
element.SetValue(FilterProperty, value);
}
public static Predicate<object> GetFilter(ItemsControl element)
{
return (Predicate<object>)element.GetValue(FilterProperty);
}
private static void OnFilterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ItemsControl itemsControl && itemsControl.Items.CanFilter)
{
if (e.OldValue is Predicate<object> oldPredicate)
{
itemsControl.Items.Filter -= oldPredicate;
}
if (e.NewValue is Predicate<object> newPredicate)
{
itemsControl.Items.Filter += newPredicate;
}
}
}
}
Source: https://stackoverflow.com/a/39438710/10927863
XAML:
<Window ...>
<Window.Resources>
<local:ItemsSourceConverter x:Key="ItemsSourceConverter"/>
</Window.Resources>
...
<ItemsControl ItemsSource="{Binding Collection1, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}"
local:CollectionViewExtensions.Filter="{Binding Filter1}"/>
<ItemsControl ItemsSource="{Binding Collection3, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}"
local:CollectionViewExtensions.Filter="{Binding Filter2}"/>
<ItemsControl ItemsSource="{Binding Collection3, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}"
local:CollectionViewExtensions.Filter="{Binding Filter3}"/>
</Window>
ViewModel:
public class ViewModel
{
ObservableCollection<DataClass> Collection1 { get; private set; }
ObservableCollection<DataClass> Collection2 { get; private set; }
ObservableCollection<DataClass> Collection3 { get; private set; }
public Predicate<object> Filter1 { get; private set; }
public Predicate<object> Filter2 { get; private set; }
public Predicate<object> Filter3 { get; private set; }
...
private void SetFilters()
{
Filter1 = (item) =>
{
// Filter logic
};
Filter2 = (item) =>
{
// Filter logic
};
Filter3 = (item) =>
{
// Filter logic
};
}
...
}

How to get ComboBox content value?

I would like to get content from my combobox. I already tried some ways to do that, but It doesn't work correctly.
This is example of my combobox:
<ComboBox x:Name="cmbSomething" Grid.Column="1" Grid.Row="5" HorizontalAlignment="Center" Margin="0 100 0 0" PlaceholderText="NothingToShow">
<ComboBoxItem>First item</ComboBoxItem>
<ComboBoxItem>Second item</ComboBoxItem>
</ComboBox>
After I click the button, I want to display combobox selected item value.
string selectedcmb= cmbSomething.Items[cmbSomething.SelectedIndex].ToString();
await new Windows.UI.Popups.MessageDialog(selectedcmb, "Result").ShowAsync();
Why this code does not work?
My result instead of showing combobox content, it shows this text:
Windows.UI.Xaml.Controls.ComboBoxItem
You need the Content property of ComboBoxItem. So this should be what you want:
var comboBoxItem = cmbSomething.Items[cmbSomething.SelectedIndex] as ComboBoxItem;
if (comboBoxItem != null)
{
string selectedcmb = comboBoxItem.Content.ToString();
}
I have expanded on my suggestion regarding using models instead of direct UI code-behind access. These are the required parts:
BaseViewModel.cs
I use this in a lot of the view models in my work project. You could technically implement it directly in a view model, but I like it being centralized for re-use.
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Hashtable values = new Hashtable();
protected void SetValue(string name, object value)
{
this.values[name] = value;
OnPropertyChanged(name);
}
protected object GetValue(string name)
{
return this.values[name];
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ComboViewModel.cs
This what you'll bind to make it easy to get values. I called it ComboViewModel because I'm only dealing with your ComboBox. You'll want a much bigger view model with a better name to handle all of your data binding.
public class ComboViewModel : BaseViewModel
{
public ComboViewModel()
{
Index = -1;
Value = string.Empty;
Items = null;
}
public int Index
{
get { return (int)GetValue("Index"); }
set { SetValue("Index", value); }
}
public string Value
{
get { return (string)GetValue("Value"); }
set { SetValue("Value", value); }
}
public List<string> Items
{
get { return (List<string>)GetValue("Items"); }
set { SetValue("Items",value); }
}
}
Window1.xaml
This is just something I made up to demonstrate/test it. Notice the various bindings.
<Window x:Class="SO37147147.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbSomething" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" HorizontalAlignment="Center" MinWidth="80"
ItemsSource="{Binding Path=Items}" SelectedIndex="{Binding Path=Index}" SelectedValue="{Binding Path=Value}"></ComboBox>
<TextBox x:Name="selectedItem" MinWidth="80" Grid.Row="2" Grid.Column="0" Text="{Binding Path=Value}" />
<Button x:Name="displaySelected" MinWidth="40" Grid.Row="2" Grid.Column="1" Content="Display" Click="displaySelected_Click" />
</Grid>
</Window>
Window1.xaml.cs
Here's the code-behind. Not much to it! Everything is accessed through the dataContext instance. There's no need to know control names, etc.
public partial class Window1 : Window
{
ComboViewModel dataContext = new ComboViewModel();
public Window1()
{
InitializeComponent();
dataContext.Items=new List<string>(new string[]{"First Item","Second Item"});
this.DataContext = dataContext;
}
private void displaySelected_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(String.Format("Selected item:\n\nIndex: {0}\nValue: {1}", dataContext.Index, dataContext.Value));
}
}
You can add business logic for populating models from a database, saving changes to a database, etc. When you alter the properties of the view model, the UI will automatically be updated.

ICommand in MVVM WPF

I'm having a look at this MVVM stuff and I'm facing a problem.
The situation is pretty simple.
I have the following code in my index.xaml page
<Grid>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<view:MovieView ></view:MovieView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and in my index.xaml.cs
...
InitializeComponent();
base.DataContext = new MovieViewModel(ent.Movies.ToList());
....
and here is my MoveViewModel
public class MovieViewModel
{
readonly List<Movies> _m;
public ICommand TestCommand { get; set; }
public MovieViewModel(List<Movies> m)
{
this.TestCommand = new TestCommand(this);
_m = m;
}
public List<Movies> lm
{
get
{
return _m;
}
}
}
finally
here is my control xaml MovieView
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label VerticalAlignment="Center" Grid.Row="0" Grid.Column="0">Title :</Label><TextBlock VerticalAlignment="Center" Grid.Row="0" Grid.Column="1" Text="{Binding Title}"></TextBlock>
<Label VerticalAlignment="Center" Grid.Row="1" Grid.Column="0">Director :</Label><TextBlock VerticalAlignment="Center" Grid.Row="1" Grid.Column="1" Text="{Binding Director}"></TextBlock>
<Button Grid.Row="2" Height="20" Command="{Binding Path=TestCommand}" Content="Edit" Margin="0,4,5,4" VerticalAlignment="Stretch" FontSize="10"/>
</Grid>
So the problem I have is that if I set ItemsSource at Binding
it doesn't make anything
if I set ItemsSource="{Binding lm}"
it populates my itemsControl but the Command (Command="{Binding Path=TestCommand}" ) doesn't not work.
Of course it doesn't not work because TestCommand doesn't not belong to my entity object Movies.
So finally my question is,
what do I need to pass to the ItemsControl to make it working?
Thx in advance
As soon as your items are rendered, each item gets set to the DataContext of the specific row it represents, so you are no longer able to reference to your first DataContext.. Also, due to the fact that you are in a DataTemplate, your bindings will start working when there is need for that Template.. so in that case you need to look up your parent control through a RelativeSource binding...
Hope that explains some things..
Try implementing the INotifyPropertyChanged interface:
public class MovieViewModel : INotifyPropertyChanged
{
readonly List<Movies> _m;
private ICommand _testCommand = null;
public ICommand TestCommand { get { return _testCommand; } set { _testCommand = value; NotifyPropertyChanged("TestCommand"); } }
public MovieViewModel(List<Movies> m)
{
this.TestCommand = new TestCommand(this);
_m = m;
}
public List<Movies> lm
{
get
{
return _m;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string sProp)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sProp));
}
}
}
What happens is that the TestCommand has a value, and the UI gets no notification that a change is taking place.. On controls you solve this problem using Dependency properties, on data object, you can use the INotifyPropertyChanged interface..
Secondly, the Movie objects have no reference to the parent object..
You can solve this problem in three different ways:
have a reference back to the model on Movie, and make the Bind path like so: ie.. if you property is named ParentMovieModel, then your Binding will be like:
{Binding Path=ParentMovieModel.TestCommand}
Make a binding based on ElementName like so: Seek up the parent control where you set your DataContext on, and give it a name: i.e. Root. Now create a binding based on the ElementName like so:
{Binding ElementName=Root, Path=DataContext.TextCommand}
Make a binding based on a RelativeSource like so: Seek up the parent control by type, and use the same path as the one above...
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type yourparentwindowtype}}, Path=DataContext.TextCommand}
Got it working
here is the thing
<ItemsControl DataContext="{Binding}" ItemsSource="{Binding lm}">
Command="{Binding Path=DataContext.TestCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
so the RelativeSource was the thing I've missed.
if somebody has a good explaination of this, I would be definitely happy.
//include namespace
using Microsoft.Practices.Composite.Wpf.Commands;
readonly List<Movies> _m;
public ICommand TestCommand { get; set; }
public MovieViewModel(List<Movies> m)
{
this.TestCommand = new DelegateCommand<object>(TestcommandHandler);
_m = m;
}
public List<Movies> lm
{
get
{
return _m;
}
}
void TestcommandHandler(object obj)
{
// add your logic here
}
}
What about <ItemsControl ItemsSource="{Binding Path=lm}"> ?
in the ItemsSource="{Binding Path=lm}"> case
the itemsControl works well but I complety bypass my MovieViewModel
and I got this in the output window
System.Windows.Data Error: 39 : BindingExpression path error: 'TestCommand' property not found on 'object' ''Movies' (HashCode=1031007)'. BindingExpression:Path=TestCommand; DataItem='Movies' (HashCode=1031007); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
Movies is my entity object and owns only the Title and Director properties

Categories

Resources