I'm developing a WPF application using caliburn.micro MVVM framework..
In-order to develop a search screen, I need to dynamically load fields into the view, based on model properties.
Consider below view and view model:
SearchViewModel
SearchView
Let's assume T is a type of Product in below example.
public class SearchViewModel<T>
{
public T Item{get;set;}
}
public class Product
{
public int Id{get;set;}
public string Name{get;set;}
public string Description{get;set;}
}
I have a user control called SearchView.xaml with no contents on it.
Whenever View is loaded new fields should be added to the view and field should be bound to the properties.
According to above code example, there are 3 public properties in the Product class, therefore 3 TextBoxes should be added to the view dynamically. When user enters data in the text field, corresponding property should be updated.
Is this possible?
Can any experts help me to achieve this by providing some examples?
I would propose going about this differently. Instead of thinking about dynamically adding properties to a view / model, I would think about adding information about those properties to a list on the viewmodel. That list would then be bound to an ItemsControl with a template that looks like a TextBox.
So your view-model would have a property on it for the "thing" you want to examine. In the setter for this property, use reflection to enumerate the properties you are interested in, and add an instance of some kind of FieldInfo class (that you create) to the list of properties with the binding.
This has the benefit of keeping everything all MVVM compatible too, and there is no need to dynamically create controls with your own code.
The example below uses my own MVVM library (as a nuget package) rather than caliburn.micro, but it should be similar enough to follow the basic idea. The full source code of the example can be downloaded from this BitBucket repo.
As you can see in the included screenshots, the search fields are created dynamically on the view without any code in the view. Everything is done on the viewmodel. This also gives you easy access to the data that the user enters.
The view-model:
namespace DynamicViewExample
{
class MainWindowVm : ViewModel
{
public MainWindowVm()
{
Fields = new ObservableCollection<SearchFieldInfo>();
SearchableTypes = new ObservableCollection<Type>()
{
typeof(Models.User),
typeof(Models.Widget)
};
SearchType = SearchableTypes.First();
}
public ObservableCollection<Type> SearchableTypes { get; }
public ObservableCollection<SearchFieldInfo> Fields { get; }
private Type _searchType;
public Type SearchType
{
get { return _searchType; }
set
{
_searchType = value;
Fields.Clear();
foreach (PropertyInfo prop in _searchType.GetProperties())
{
var searchField = new SearchFieldInfo(prop.Name);
Fields.Add(searchField);
}
}
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get { return _searchCommand ?? (_searchCommand = new SimpleCommand((obj) =>
{
WindowManager.ShowMessage(String.Join(", ", Fields.Select(f => $"{f.Name}: {f.Value}")));
})); }
}
}
}
The SearchFieldInfo class:
namespace DynamicViewExample
{
public class SearchFieldInfo
{
public SearchFieldInfo(string name)
{
Name = name;
}
public string Name { get; }
public string Value { get; set; } = "";
}
}
The view:
<Window
x:Class="DynamicViewExample.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:DynamicViewExample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance local:MainWindowVm}"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
ItemsSource="{Binding Path=SearchableTypes}"
SelectedItem="{Binding Path=SearchType}" />
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Fields}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBox Width="300" Text="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Grid.Row="2" Command="{Binding Path=SearchCommand}">Search</Button>
</Grid>
</Window>
The model classes:
class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Id { get; set; }
}
class Widget
{
public string ModelNumber { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Here is a basic example of how you could generate a TextBox per public property of the T in the control using reflection.
SearchView.xaml:
<Window x:Class="WpfApplication4.SearchView"
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:WpfApplication4"
mc:Ignorable="d"
Title="SearchView" Height="300" Width="300">
<StackPanel x:Name="rootPanel">
</StackPanel>
</Window>
SearchView.xaml.cs:
public partial class SearchView : UserControl
{
public SearchView()
{
InitializeComponent();
DataContextChanged += SearchView_DataContextChanged;
DataContext = new SearchViewModel<Product>();
}
private void SearchView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
Type genericType = e.NewValue.GetType();
//check the DataContext was set to a SearchViewModel<T>
if (genericType.GetGenericTypeDefinition() == typeof(SearchViewModel<>))
{
//...and create a TextBox for each property of the type T
Type type = genericType.GetGenericArguments()[0];
var properties = type.GetProperties();
foreach(var property in properties)
{
TextBox textBox = new TextBox();
Binding binding = new Binding(property.Name);
if (!property.CanWrite)
binding.Mode = BindingMode.OneWay;
textBox.SetBinding(TextBox.TextProperty, binding);
rootPanel.Children.Add(textBox);
}
}
}
}
}
The other option will obviously be to create a "static" view for each type of T and define the TextBox elements in the XAML markup as usual.
Related
I have a custom User Control that has a ListView.
I also have a simple object hierarchy where 'Group' 'Questions' and so on all inherit from BaseEntity.
I am trying to create a user control that can bind to any BaseEntity type and show its Name and Id(base type properties). this way I can re-use it throughout the application for whatever ObservableCollection basic details I want to display in the control
it works when I bind an ObservableCollection of BaseEntity but not a specific type. unless I change the dependency properties to be that specific type - but that defeats the whole point.
How can I cast down to the base type on binding in xaml?
so I could bind with collections of:
ObservableCollection<Group>//inherits from BaseEntity
or
ObservableCollection<OtherType>//inherits from BaseEntity
or
ObservableCollection<BaseEntity> //its a BaseEntity
ideally, something like {Binding Path=(BaseEntity)OwnedGroups} - but it's not being that straight forward
==================the Control.xaml.cs==============
public partial class InOrOutControl : UserControl
{
public ObservableCollection<BaseEntity> EntitiesOwned
{
get { return (ObservableCollection<BaseEntity>)GetValue(EntitiesOwnedProperty); }
set { SetValue(EntitiesOwnedProperty, value); }
}
public static readonly DependencyProperty EntitiesOwnedProperty =
DependencyProperty.Register(nameof(EntitiesOwned), typeof(ObservableCollection<BaseEntity>), typeof(InOrOutControl), new PropertyMetadata(new ObservableCollection<BaseEntity>(), SetOwnedItemsSource));
private static void SetOwnedItemsSource(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
InOrOutControl controll = d as InOrOutControl;
if (controll != null)
{
controll.OutItemsLV.ItemsSource = e.NewValue as ObservableCollection<BaseEntity>;
}
}
public InOrOutControl()
{
InitializeComponent();
}
}
=================Control xaml=================
<UserControl x:Class="HonorsProject.View.CustomControlls.InOrOutControl"
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:HonorsProject.View.CustomControlls"
xmlns:appCore="clr-namespace:HonorsProject.Model.Core;assembly=HonorsProject.Model"
mc:Ignorable="d"
Name="InOrOutCtrl"
d:DesignHeight="450" d:DesignWidth="800">
<ListView Name="OutItemsLV"
ItemsSource="{Binding EntitiesOwned, ElementName=InOrOutCtrl}"
Grid.Row="1"
Height="100"
Grid.Column="0">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
==============the control in the Page===============
<uc:InOrOutControl x:Name="InOrOutControl"
EntitiesOwned="{Binding Path=OwnedGroups,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
===============Base Type========
public class BaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
=============Specific type========
public class Group : BaseEntity
{
public virtual List<Student> Students { get; set; }
public int SomeSpecificProperty {get;set;}
public Group()
{
Students = new List<Student>();
}
}
I have a WPF MVVM project which has a property of type OudPattern.
I would like to bind this property to OudPatternEditor1 which is a UserControl in view. is there any way to do this using INotifyPropertyChange. Since I'm already using it in my project.
Please notice that OudPatternEditor1 is instantiated at the startup using a new OudPattern(). But this pattern can be changed by user. so I need to update this property in the view model and OudEditor1 also.
I solved the problem by passed the view to the view model and updating this property in there each time. But I know that this approach is against the MVVM principles of decoupling view and view model.
I also tried to solve this using dependency property but i failed to get a solution.
Custom user control XAML
<UserControl x:Class="MyOudTeacher.OudMachine.OudPatternEditor"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Visibility="Visible">
<Canvas x:Name="oudGridCanvas"/>
</UserControl>
Code-behind
public partial class OudPatternEditor : UserControl
{
private OudPattern oudPattern;
private double gridSquareWidth = 15;// my code: default 20
private double namesColumnWidth = 50;//my code: default 100
public OudPatternEditor()
{
InitializeComponent();
this.oudPattern = new OudPattern();
DrawNoteNames();
DrawPattern(namesColumnWidth);
DrawGridLines(namesColumnWidth);
}
public OudPattern OudPattern
{
get { return oudPattern; }
set {
oudPattern = value;
UpdateHitView();
}
}
/* more code... */
}
The view XAML
<UserControl x:Class="MyOudTeacher.OudMachine.OudMachineView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500" xmlns:my="clr-namespace:MyOudTeacher.OudMachine">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="262*" />
</Grid.RowDefinitions>
<my:OudPatternEditor Grid.Row="1" HorizontalAlignment="Left" Margin="20" x:Name="oudPatternEditor1" VerticalAlignment="Top"/>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding StopCommand}" Margin="2" ToolTip="Stop">
<Rectangle Fill="DarkBlue" Width="15" Height="15" Margin="3" RadiusX="2" RadiusY="2"/>
</Button>
<!-- more code... -->
view model
class OudMachineViewModel : ViewModelBase, IDisposable
{
private IWavePlayer waveOut;
private OudPattern pattern;
private OudPatternEditor OudPatternEditor;
private OudPatternSampleProvider patternSequencer;
private int tempo;
private string selectedFile;
public ICommand PlayCommand { get; }
public ICommand StopCommand { get; }
public ICommand UpdateXmlCommand { get; }
public ICommand OpenFileCommand { get; }
public ICommand PauseCommand { get; }
public OudMachineViewModel(OudPatternEditor oudPatternEditor)
{
this.OudPattern = oudPatternEditor.OudPattern;
this.OudPatternEditor = oudPatternEditor;
Tempo = OudPattern.ScoreHits.ScaledTempo;
PlayCommand = new DelegateCommand(Play);
StopCommand = new DelegateCommand(Stop);
OpenFileCommand = new DelegateCommand(OpenFile);
PauseCommand = new DelegateCommand(Pause);
}
public OudPattern OudPattern
{
get { return pattern; }
set
{
pattern = value;
OnPropertyChanged("OudPattern");
}
}
private void UpdateSelectedFile()
{
OudPattern = new OudPattern(selectedFile);
OudPatternEditor.OudPattern = this.OudPattern;
Tempo = OudPattern.ScoreHits.ScaledTempo;
}
/* more code... */
The correct way is NOT to pass OudEditor to the view model constructor. since this is against MVVM. I expect to find a way to update OudEditor1.OudPattern by binding it to ViewModel.OudPattern
Edit #1:
I was able to add a dependency property and achieve what I wanted. The code added is simple:
view code-behind:
public OudPattern OudPattern
{
get { return (OudPattern)GetValue(OudPatternProperty); }
set
{
SetValue(OudPatternProperty, value);
DrawNewPattern();
UpdateHitView();
}
}
public static readonly DependencyProperty OudPatternProperty =
DependencyProperty.Register("OudPattern", typeof(OudPattern), typeof(OudPatternEditor),
new PropertyMetadata(default(OudPattern)));
view XAML above, modified:
<my:OudPatternEditor Grid.Row="1" HorizontalAlignment="Left" Margin="20" x:Name="oudPatternEditor1" VerticalAlignment="Top" OudPattern="{Binding OudPattern}"/>
view model:
public OudPattern OudPattern
{
get { return pattern; }
set
{
pattern = value;
OnPropertyChanged("OudPattern");
}
}
But I noticed that the setter for the dependency property in my view code-behind never executes. I learned that those setters are not executed when called from outside. How do I make the view code-behind react to external change and do the methods mentioned in the setter above? i.e.
DrawNewPattern();
UpdateHitView();
Edit# 2:
I found an answer to my last question about dependency property setter. Thanks for Elgonzo to point out that I can use property changed callbacks.
The code should be modified to add OnPatternChanged callback
public static readonly DependencyProperty OudPatternProperty =
DependencyProperty.Register("OudPattern", typeof(OudPattern), typeof(OudPatternEditor),
new PropertyMetadata(default(OudPattern),OnPatternChanged));
private static void OnPatternChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myObject = (OudPatternEditor)d;
myObject.DrawNewPattern();
myObject.UpdateHitView();
Note that you cant call none static methods directly from static OnPatternChanged.. so you need to cast the sender into object and call its member methos.
I'm trying to use a TabControl to switch between UserControls.
I could just set the content of the tabs to the usercontrols with XAML but then it will only be bound to the view and not the viewmodel.
My VM is a Caliburn.Micro Conductor and it calls ActivateItem whenever the user switches tabs. It worked fine when I only have one usercontrol, but when I created another one the first one will not load the view.
Here's some of the code I'm using:
ShellView:
<dx:ThemedWindow x:Class="PSCServiceManager.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Title="Service Manager" WindowState="Maximized"
Height="525" Width="720">
<Grid>
<dx:DXTabControl>
<dx:DXTabItem Header="Master Teknisi">
<ContentControl x:Name="LoadMasterTechnicianView" cal:View.Model="{Binding ActiveItem}" />
</dx:DXTabItem>
<dx:DXTabItem Header="Servisan">
<ContentControl x:Name="LoadServicesView" cal:View.Model="{Binding ActiveItem}" />
</dx:DXTabItem>
</dx:DXTabControl>
</Grid>
ShellViewModel:
using Caliburn.Micro;
namespace PSCServiceManager.ViewModels
{
public class ShellViewModel : Conductor<IScreen>
{
private MasterTechnicianViewModel masterTechnicianViewModel;
private ServicesViewModel servicesViewModel;
public ShellViewModel()
{
LoadMasterTechnicianView();
}
public void LoadMasterTechnicianView()
{
ActivateItem(masterTechnicianViewModel);
}
public void LoadServicesView()
{
ActivateItem(servicesViewModel);
}
}
}
An easier/alternative way to implement this would be to create a collection of User Controls you would like to bind to the Tab Control. For example,
public interface ITabUserControl
{
string DisplayName { get; set; }
}
public class MasterTechnicianViewModel : ITabUserControl
{
public string DisplayName { get; set; } = "Master Technician";
}
public class ServicesViewModel : ITabUserControl
{
public string DisplayName { get; set; } = "Services";
}
Now in your ShellViewModel, you could create a Collection of ITabUserControl
public List<ITabUserControl> UserControls { get; set; }
public ShellViewModel()
{
UserControls = new List<ITabUserControl>();
UserControls.Add(new MasterTechnicianViewModel());
UserControls.Add(new ServicesViewModel());
}
And bind your TabControl as
<dx:DXTabControl x:Name="UserControls"/>
Now you can switch between the controls without any issues, without Activating it explicitly.
I'm trying to build a DTO to store the software configuration, but I'm stuck because my view is not sending the data to my ViewModel and also to my DTO.
I need to transfer 2 textbox and 3 combobox to my DTO, but using this code the values are always empty.
My ViewModel:
public class ViewModelProcessamentoArquivo : ViewModelBase
{
private PesquisaConfiguracao pesquisaConfiguracao;
public PesquisaConfiguracao PesquisaConfiguracao
{
get { return pesquisaConfiguracao; }
set
{
pesquisaConfiguracao = value;
base.OnPropertyChanged("PesquisaConfiguracao");
}
}
}
My DTO/Model
public class PesquisaConfiguracao
{
public string ArquivoOrigem { get; set; }
public string ArquivoDestino { get; set; }
public string TipoPesquisa { get; set; }
public string PesquisaVeicular { get; set; }
public string PesquisaCrediticia { get; set; }
}
And my View is like this.
<TextBox Name="txtBuscarArquivoOrigem" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Height="30" Margin="10, 0" Text="{Binding PesquisaConfiguracao.ArquivoOrigem}" />
<TextBox x:Name="txtBuscarArquivoDestino" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="3" Height="30" Margin="10, 0" Text="{Binding PesquisaConfiguracao.ArquivoDestino}" IsEnabled="false" />
...
Do you guys know why it's happening? I've used something similar in my other project and worked just fine. Also if you have any other possibly way to fix this issue, please comment!
First UpdateSourceTrigger PropertyChanged, that way the target (view) will update your source object on every change:
<TextBox Name="txtBuscarArquivoOrigem" Height="30" Text="{Binding PesquisaConfiguracao.ArquivoOrigem, UpdateSourceTrigger=PropertyChanged}" />
Then implement in your source object the INotifyPropertyChange Interface on it's properties in order to update the view when the value has changed:
private string _arquivoOrigem;
public string ArquivoOrigem
{
get
{
return _arquivoOrigem;
}
set
{
_arquivoOrigem = value;
OnPropertyChanged("ArquivoOrigem");
}
}
Put a BreakPoint in the property setter and it will break there when you change the value in the view TextBox.
If it doesn't work for you probably forgot to set your DataContext to your ViewModel:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow"
DataContext="{StaticResource MainViewModel}">
Or did not initialize your source object:
public MainViewModel()
{
pesquisaConfiguracao = new PesquisaConfiguracao
{
ArquivoDestino = "aaa",
ArquivoOrigem = "bbb",
PesquisaCrediticia = "ccc",
PesquisaVeicular = "dddd",
TipoPesquisa = "eee"
};
}
I've created an unfoldable list of checkboxes like so.
<Expander x:Name=...>
<ListBox ItemsSource="{x:Static local:MainWindow.AllTypes}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"
Checked="ToggleButton_OnToggled"
Unchecked="ToggleButton_OnToggled"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
I also have a method with the signature below.
private void FilterStuffOut(String condition)
{
CollectionViewSource source
= new CollectionViewSource { Source = dataGrid.ItemsSource };
ICollectionView view = source.View;
view.Filter = element => BringItOut(element, condition);
dataGrid.ItemsSource = view;
}
I'm unsure (and poking around with intellisense both in sender and eventArgs gave me nothing) how to get to know which checkbox is the firey one. Where should I look for it in the method below?
private void ToggleButton_OnToggled(
Object sender, RoutedEventArgs eventArgs) { ... }
You would typically write it like shown below, explicitly not using the as operator, but casting to the desired types. This is because you expect those types, and any other type should result in a runtime error, i.e. an InvalidCastException.
private void ToggleButton_OnToggled(object sender, RoutedEventArgs eventArgs)
{
var element = (FrameworkElement)sender;
var myType = (MyType)element.DataContext;
// do something with myType.MyValue
}
If you would need properties of more derived types, e.g. ToggleButton.IsChecked, you would use that type instead of FrameworkElement.
As promised.
I tend to use Movies for my examples, so please excuse my habit. I wasn't certain exactly what your UI looked like, or what you were trying to achieve here, but I think I understood the general idea.
If you're not familiar with the MVVM design pattern, I'd advise working through this tutorial.
Here we go.
These are my models:
//This is a base class which handles our notify property changed stuff which will update the UI
//when properties change.
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The class above is the base class for all models and sometimes view models if the view model requires it. It's pretty useful and I'd advise that you implement something like this in all of your MVVM applications.
public class Filter : NotifyPropertyChangedBase
{
public event EventHandler OnEnabledChanged;
public string Genre { get; set; }
private bool _IsEnabled;
public bool IsEnabled
{
get { return _IsEnabled; }
set
{
_IsEnabled = value;
OnPropertyChanged();
if (OnEnabledChanged != null)
OnEnabledChanged(this, new EventArgs());
}
}
public Filter(string genre)
{
this.Genre = genre;
}
}
public class Movie
{
//We don't need to implement INotifyPropertyChanged here
//because these values will never change.
public string Name { get; set; }
public string Genre { get; set; }
}
The models above represent movies, and a filter for the genre of a movie. All models do not "do" anything, they simply represent data.
And here is the ViewModel:
public class MovieViewModel : NotifyPropertyChangedBase
{
private ObservableCollection<Movie> _FilteredMovies;
public ObservableCollection<Movie> FilteredMovies
{
get { return _FilteredMovies; }
set
{
_FilteredMovies = value;
//Need to implement INotifyPropertyChanged here because
//I am instantiating a new observable collection in the enabled changed
//method. This will refresh the binding on the DataGrid.
OnPropertyChanged();
}
}
public ObservableCollection<Movie> Movies { get; set; }
public ObservableCollection<Filter> Filters { get; set; }
public MovieViewModel()
{
this.Movies = new ObservableCollection<Movie>();
this.Filters = new ObservableCollection<Filter>();
#region Sample Data
this.Movies.Add(new Movie()
{
Name = "Movie Action",
Genre = "Action"
});
this.Movies.Add(new Movie()
{
Name = "Movie Romance",
Genre = "Romance"
});
this.Movies.Add(new Movie()
{
Name = "Movie Comedy",
Genre = "Comedy"
});
this.Filters.Add(new Filter("Action"));
this.Filters.Add(new Filter("Romance"));
this.Filters.Add(new Filter("Comedy"));
foreach (Filter filter in this.Filters)
filter.OnEnabledChanged += filter_OnEnabledChanged;
#endregion
}
void filter_OnEnabledChanged(object sender, EventArgs e)
{
var filteredMovies = (from m in this.Movies
join f in this.Filters on m.Genre equals f.Genre
where f.IsEnabled
select m).ToList();
this.FilteredMovies = new ObservableCollection<Movie>(filteredMovies);
}
}
It has a collection of movies, and a collection of filters. When a checkbox is selected, the OnEnabledChanged method will be called and set the FilteredMovies property to that of the selected filters. This in turn will call the notify property changed code and update the UI.
Here is the UI:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<ViewModels:MovieViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Expander>
<ItemsControl ItemsSource="{Binding Filters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsEnabled}"
Content="{Binding Genre}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
<DataGrid Grid.Row="1"
ItemsSource="{Binding FilteredMovies}"/>
</Grid>
Similar to your implementation, there is a DataGrid which is bound to the FilteredMovies property in the ViewModel, and the list of Filters is represented as a list of CheckBox objects.
Like I mentioned earlier, I wasn't exactly sure what you were trying to implement, but I think this was something of what you were trying to do.