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.
Related
I have the below problem: I have two different user controls inside a parent user control. These are trainList, which holds a list of train objects and trainView, which is an user control that shows details of the selected train in the list.
My wish is to share a variable of trainList with trainView.
What I have now is:
Parent user control:
<UserControl>
<UserControl>
<customControls:trainList x:Name="trainList"></customControls:trainList>
</UserControl>
<UserControl>
<customControls:trainView x:Name="trainView"></customControls:trainView>
</UserControl>
<TextBlock DataContext="{Binding ElementName=trainList, Path=SelectedTrain}" Text="{ Binding SelectedTrain.Id }">Test text</TextBlock>
</UserControl>
TrainList class:
public partial class TrainList : UserControl
{
public TrainList()
{
this.InitializeComponent();
this.DataContext = this;
}
public Train SelectedTrain { get; set; }
public void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Debug.Print(this.SelectedTrain.Id);
}
}
Note: The Train class implements INotifyPropertyChanged.
If I got this to work, I'd apply the binding to the trainView user control (not sure if this would work) instead to the text block.
<UserControl>
<customControls:trainView x:Name="trainView" DataContext="{Binding ElementName=trainList, Path=SelectedTrain}"></customControls:trainView>
</UserControl>
And then, I would access that variable someway from the code-behind of trainView.
(And after this, I would like to share a different variable from trainView with its parent user control, but maybe that's another question).
My current question is: could this be done this way or would I need to follow another strategy?
Take this simple view model, with a base class that implements the INotifyPropertyChanged interface, and a Train, TrainViewModel and MainViewModel class.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(
ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (!Equals(storage, value))
{
storage = value;
OnPropertyChanged(propertyName);
}
}
}
public class Train : ViewModelBase
{
private string name;
public string Name
{
get { return name; }
set { SetValue(ref name, value); }
}
private string details;
public string Details
{
get { return details; }
set { SetValue(ref details, value); }
}
// more properties
}
public class TrainViewModel : ViewModelBase
{
public ObservableCollection<Train> Trains { get; }
= new ObservableCollection<Train>();
private Train selectedTrain;
public Train SelectedTrain
{
get { return selectedTrain; }
set { SetValue(ref selectedTrain, value); }
}
}
public class MainViewModel
{
public TrainViewModel TrainViewModel { get; } = new TrainViewModel();
}
which may be initialized in the MainWindow's constructor like this:
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
DataContext = vm;
vm.TrainViewModel.Trains.Add(new Train
{
Name = "Train 1",
Details = "Details of Train 1"
});
vm.TrainViewModel.Trains.Add(new Train
{
Name = "Train 2",
Details = "Details of Train 2"
});
}
The TrainDetails controls would look like this, of course with more elements for more properties of the Train class:
<UserControl ...>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Details}"/>
</StackPanel>
</UserControl>
and the parent UserControl like this, where I directly use a ListBox instead of a TrainList control:
<UserControl ...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Trains}"
SelectedItem="{Binding SelectedTrain}"
DisplayMemberPath="Name"/>
<local:TrainDetailsControl Grid.Column="1" DataContext="{Binding SelectedTrain}"/>
</Grid>
</UserControl>
It would be instantiated in the MainWindow like this:
<Grid>
<local:TrainControl DataContext="{Binding TrainViewModel}"/>
</Grid>
Note that in this simple example the elements in the UserControls' XAML bind directly to a view model instance that is passed via their DataContext. This means that the UserControl know the view model (or at least their properties). A more general approach is to declare dependency properties in the UserControl class, that are bound to view model properties. The UserControl would then be independent of any particular view model.
Ok, so I normally wait until the last possible moment to submit a question but I could really use some help.
I'm attempting to bind an ObservableCollection<Contact_Info> to a WPF TreeView Control. Contact_Info contains properties for contact information and implements INotifyPropertyChanged. It also contains a list of more detailed information for the given Contact.
I'm having trouble with 2 things. 1) I need to display on the TreeViewItem header the name of the Company however, a lot of the Contacts have a missing Company Name in which, I want to use the First and Last name. I have attempted to use things like FallBackValue and TargetNullValue but the CompanyName is not null, its just empty so I can't seem to get that right. Do I need a converter? 2) I cannot seem to get the hierarchy correct in that, I want all Contacts of the same AccountID to be grouped together within a TreeViewItem. Currently, I can only get the Company Name to display on the TreeViewItem and expanding it displays a blank value and there are still duplicate Company Names displayed..
Code for my attempt at a HierarchicalDataTemplate placed under the DockPanel.Resources (not sure if that's the right place either).
<local:Main_Interface x:Key="MyList" />
<HierarchicalDataTemplate DataType = "{x:Type
local:Contact_Info}" ItemsSource = "{Binding Path=OppDataList}">
<TextBlock Text="{Binding Path=CompanyName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType = "{x:Type sharpspring:SharpspringOpportunityDataModel}" >
<TextBlock Text="{Binding Path=ProjectName}"/>
</DataTemplate>
The above code is the most recent thing I have tried among countless other ways. I feel that I'm really close but at this point I could really use some help from SO..
The other classes are a little long so let me know if you need more. Any help provided is greatly appreciated!
Edit 1: Contact_Info
public class Contact_Info : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal SharpspringLeadDataModel LeadDataModel;
internal SharpspringOpportunityDataModel OppData;
public List<SharpspringOpportunityDataModel> OppDataList { get; set; }// Should i make this ObservableCollection?
public Project_Info ProjectInfo { get; set; }
public bool IsDirty { get; set; }
public bool IsNew { get; set; }
#region Properties
public long AccountID
{
get => LeadDataModel.AccountID;
set
{
if (value != LeadDataModel.AccountID)
{
LeadDataModel.AccountID = value;
NotifyPropertyChanged();
}
}
}
public long LeadID
{
get => LeadDataModel.LeadID;
set
{
if (value != LeadDataModel.LeadID)
{
LeadDataModel.LeadID = value;
NotifyPropertyChanged();
}
}
}
// Lead Info
public string CompanyName
{
get => LeadDataModel.CompanyName;
set
{
if (value != LeadDataModel.CompanyName)
{
LeadDataModel.FaxNumber = value;
NotifyPropertyChanged();
}
}
}
public Contact_Info(SharpspringLeadDataModel lead, SharpspringOpportunityDataModel opp)
{
LeadDataModel = lead;
OppData = opp;
ProjectInfo = new Project_Info(this);
}
public SharpspringLeadDataModel GetDataModel()
{
return LeadDataModel;
}
public SharpspringOpportunityDataModel GetOppModel()
{
return OppData;
}
public Project_Info GetProjectInfoModel()
{
return ProjectInfo;
}
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
IsDirty = true;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Edit 2: Currently populates tree but does not group Companies together...
private void PopulateTreeView(List<SharpspringLeadDataModel> lead_list, List<SharpspringOpportunityDataModel> opp_list)
{
Contacts = new ObservableCollection<Contact_Info>();
var complete2 =
from lead in lead_list
let oppList = from o in opp_list
where o.PrimaryLeadID == lead.LeadID
select o
select new Contact_Info()
{
LeadDataModel = lead,
OppData = oppList.DefaultIfEmpty(new SharpspringOpportunityDataModel()).First(),
OppDataList = oppList.ToList(),
};
Contacts = complete2.ToObservableCollection();
Lead_Treeview.ItemsSource = Contacts;
}
I think your problem is really with using List instead of ObservableCollection for OppDataList. Using your code (reduced to minimal needed) but with the ObservableCollection driving the collection it works fine for me.
Note that my code will work fine with List even because its all static but in a proper app you'll need OC to track changes in collection.
Disclaimer: I'm using code behind to answer this question to keep it simple but I do not recommend any such use in any real app
MainWindow.xaml
<Window x:Class="WpfApp1.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"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="240" Width="320">
<TreeView ItemsSource="{Binding Contacts}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Contact_Info}" ItemsSource="{Binding Path=OppDataList}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type local:SharpspringOpportunityDataModel}">
<TextBlock Text="{Binding Path=ProjectName}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Path=CompanyName}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using WpfApp1.Annotations;
namespace WpfApp1
{
public partial class MainWindow : INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<Contact_Info> Contacts { get; } =
new ObservableCollection<Contact_Info>
{
new Contact_Info
{
CompanyName = "Apple",
OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}}
},
new Contact_Info
{
CompanyName = "Google",
OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}}
},
new Contact_Info
{
CompanyName = "Microsoft",
OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}}
}
};
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Contact_Info
{
public string CompanyName { get; set; }
public ObservableCollection<SharpspringOpportunityDataModel> OppDataList { get; } =
new ObservableCollection<SharpspringOpportunityDataModel>();
}
public class SharpspringOpportunityDataModel
{
public string ProjectName { get; set; }
}
}
Screenshot
based on my understanding your OppDataList is the sub property for the given Contract info and you want it to be collapsible.
In such case you can do
<ListBox ItemsSource="{Binding YourContactList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding ProjectName}">
<Expander Header="{Binding CompanyName}">
<ListBox ItemsSource="{Binding OppDataList}">
<!--Your OppDataList display-->
</ListBox>
</Expander>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
End result will look similar to this
+Contract1
-Contract2
-Project2
-Company2
-Opp
1
2
3
+Contract3
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.
I want to add Sort functionality on top of two Filters that I have implemented.
This is what my code looks like at the moment.
XAML
<Grid Grid.Row="1">
<StackPanel Orientation="Horizontal">
<Border BorderThickness="1" BorderBrush="Red">
<ComboBox
Name="SortComboBox"
SelectionChanged="View_SelectionChanged"
ItemsSource="{Binding sortOptions, Mode=OneWay}"
SelectedValue="{Binding selectedSortOption}"
/>
</Border>
<ComboBox
Name="GenreComboBox"
SelectionChanged="View_SelectionChanged"
ItemsSource="{Binding genreOptions, Mode=OneWay}"
SelectedValue="{Binding selectedGenreOption}"
/>
<TextBox Name="SearchTextBox" Width="154" TextChanged="Search_SelectionChanged" Text="{Binding Path=searchTerm, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Grid>
The first SortComboBox is used to sort movies while the GenreComboBox filters movies based on genre. The SearchTextBox is another filter to find movies by keywords.
Behind Code
private void View_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_moviePanelVM.DisplayMovies.View.Refresh();
}
private void Search_SelectionChanged(object sender, TextChangedEventArgs e)
{
_moviePanelVM.DisplayMovies.View.Refresh();
}
ViewModel
public MoviePanelViewModel()
{
...
...
this.DisplayMovies = new CollectionViewSource();
this.DisplayMovies.Source = this.Movies;
this.DisplayMovies.Filter += GenreFilter;
this.DisplayMovies.Filter += SearchFilter;
}
private void GenreFilter(object sender, FilterEventArgs e)
{
MediaDetail media = e.Item as MediaDetail;
if (selectedGenreOption != "All" && !media.genre.Contains(selectedGenreOption))
e.Accepted = false;
}
private void SearchFilter(object sender, FilterEventArgs e)
{
MediaDetail media = e.Item as MediaDetail;
if (!media.title.ToLower().Contains(searchTerm.ToLower()))
e.Accepted = false;
}
The SortComboBox may have a selected value of A-Z in which case I would sort a particular way. Or it may have a value of Z-A in which case I would sort it another way.
My question is, where should I be adding SortDescriptions and the logic to control what kind of sort should occur in order to ensure that the MVVM pattern is maintained?
UPDATE
Code Behind
private void Sort_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_moviePanelVM.SortMovies();
}
Changed my XAML to:
<ComboBox
Name="SortComboBox"
SelectionChanged="Sort_SelectionChanged"
ItemsSource="{Binding sortOptions, Mode=OneWay}"
SelectedValue="{Binding selectedSortOption}"
/>
ViewModel
public void SortMovies()
{
DisplayMovies.SortDescriptions.Clear();
switch (SelectedSortOption)
{
case "A-Z":
DisplayMovies.SortDescriptions.Add(new SortDescription("title", ListSortDirection.Ascending)); break;
case "Z-A":
DisplayMovies.SortDescriptions.Add(new SortDescription("title", ListSortDirection.Descending)); break;
case "Release Date":
DisplayMovies.SortDescriptions.Add(new SortDescription("year", ListSortDirection.Descending)); break;
case "Rating":
DisplayMovies.SortDescriptions.Add(new SortDescription("rating", ListSortDirection.Descending)); break;
}
}
This works but I was wondering if I should be doing it like this? As the filters were simply Refreshing the view but with sort I'm calling a function in the ViewModel.
I think you are unclear on some of the principles of the MVVM design pattern. Ideally you do not want any code behind on the view at all, this includes events.
You need to replace events with commands. Click here for a tutorial.
In the following example, I am not using any events on the view, but instead I am using a command to bind the change of the sorting to filter the movies.
Here are the models I am using:
public class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Movie : PropertyChangedBase
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
}
Here is the command:
public class SortChangedCommand : ICommand
{
MoviesViewModel _viewModel;
public SortChangedCommand(MoviesViewModel viewModel)
{
_viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_viewModel.Sort(parameter as string);
}
}
Note that the command holds a reference to the view model, and it simply calls the Sort method when the command gets executed.
Here is the view model:
public class MoviesViewModel : PropertyChangedBase
{
public ObservableCollection<Movie> Movies { get; set; }
private ObservableCollection<Movie> _FilteredMovies;
public ObservableCollection<Movie> FilteredMovies
{
get { return _FilteredMovies; }
set
{
_FilteredMovies = value;
//Have to implement property changed because in the sort method
//I am instantiating a new observable collection.
OnPropertyChanged("FilteredMovies");
}
}
public SortChangedCommand SortChangedCommand { get; set; }
public MoviesViewModel()
{
this.Movies = new ObservableCollection<Movie>();
#region Test Data
this.Movies.Add(new Movie()
{
Name = "Movie 1"
});
this.Movies.Add(new Movie()
{
Name = "Movie 2"
});
this.Movies.Add(new Movie()
{
Name = "Movie 3"
});
#endregion
//Copy the movies list to the filtered movies list (this list is displayed on the UI)
this.FilteredMovies = new ObservableCollection<Movie>(this.Movies);
this.SortChangedCommand = new SortChangedCommand(this);
}
public void Sort(string sortOption)
{
switch (sortOption)
{
case "A-Z": this.FilteredMovies = new ObservableCollection<Movie>(this.Movies.OrderBy(x => x.Name)); break;
case "Z-A": this.FilteredMovies = new ObservableCollection<Movie>(this.Movies.OrderByDescending(x => x.Name)); break;
}
}
}
The view model contains two lists, one to hold all movies, and the other to hold a list of filtered movies. The filtered movies will be displayed on the UI.
Here is the view:
<Window x:Class="BorderCommandExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:BorderCommandExample"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ViewModels:MoviesViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Command="{Binding SortChangedCommand}" CommandParameter="A-Z"
Content="A-Z"/>
<Button Command="{Binding SortChangedCommand}" CommandParameter="Z-A"
Content="Z-A"
Grid.Row="1"/>
<ListBox ItemsSource="{Binding FilteredMovies}"
DisplayMemberPath="Name"
Grid.Row="2"/>
</Grid>
Note: I didn't use a ComboBox, as the ComboBox control doesn't have the Command properties, but you can easily get around this.
When you click on any of the buttons, the command will execute and call the sort method. And the UI will then update with the filtered movies.
The UI itself is probably not what you were after, however the use of a command to achieve this is what you need.
So I'm brand new to WPF data binding, and it is.. complicated. At this point, I'm trying to just create a list of premade test items and have it displayed in a listbox with a data template when I press a button. After hours of puzzling through tutorials and MSDN this is the best I could come up with.
The data item I want to make a list from:
class ListingItem
{
private string title;
private string user;
private string category;
//Dummy constructor for test purposes
public ListingItem()
{
title = "TestTitle";
user = "TestUser";
category = "TestCatagory";
}
}
The quick and dirty list creator:
class ListMaker
{
public static List<ListingItem> getListing()
{
List<ListingItem> listing = new List<ListingItem>();
for(int i = 0; i <100; i++)
{
listing.Add(new ListingItem());
}
return listing;
}
}
The XAML of the list itself:
<ListBox x:Name="Listing">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="Gray" Margin="25,0,0,0" Text="{Binding user}"/>
<TextBlock Foreground="Gray" Margin="25,0,0,0" Text="{Binding category}"/>
</StackPanel>
<TextBlock Foreground="Black" Width="270" TextWrapping="Wrap" Text="{Binding title}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
And finally, the button click event which is SUPPOSED to make the magic happen:
private void TabClickEvent(object sender, RoutedEventArgs e)
{
Listing.DataContext = RedditScanner.getListing();
}
Problem is, obviously, the magic is not happening. No errors or anything so easy, I just press that button and dont see any change to the list box. Any help with this?
You cannot bind to private fields. Not even to public fields I think.
Use properties:
class ListingItem
{
//private string title;
//private string user;
//private string category;
public string Title { get; set; }
public string User { get; set; }
public string Category { get; set; }
//Dummy constructor for test purposes
public ListingItem()
{
Title = "TestTitle";
User = "TestUser";
Category = "TestCatagory";
}
}
And for full databinding you would have to implement INotifyPropertyChanged on ListingItem.
the magic is not happening. No errors or anything so easy,
Keep an eye on the Output Window during execution. Binding errors are reported.
Made some minor changes to your code as explained below.
class ListingItem
{
public string title { get; set; }
public string user { get; set; }
public string category { get; set; }
//Dummy constructor for test purposes
public ListingItem()
{
title = "TestTitle";
user = "TestUser";
category = "TestCatagory";
}
}
The list item class, I changed the title, user and category to properties (get;set;). I also needed to make them public so they could be accessed through the binding.
class ListMaker
{
public static List getListing()
{
List listing = new List();
for (int i = 0; i < 100; i++)
{
listing.Add(new ListingItem());
}
return listing;
}
}
No changes to your ListMaker class
public class CommandHandler : ICommand
{
private Action _action;
private bool _canExecute;
public CommandHandler(Action action, bool canExecute=true)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
}
I introduced a new class to be able to bind the button. This kind of class if relatively common
<Window x:Class="SimpleDatabinding.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:viewmodel="clr-namespace:SimpleDatabinding" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <viewmodel:MainWindowViewModel/> </Window.DataContext> <Grid> <DockPanel> <Button Command="{Binding FillListCommand}" DockPanel.Dock="Top">Fill List</Button> <ListBox ItemsSource="{Binding Listing}" DockPanel.Dock="Top"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Vertical"> <StackPanel Orientation="Horizontal"> <TextBlock Foreground="Gray" Margin="25,0,0,0" Text="{Binding user}"/> <TextBlock Foreground="Gray" Margin="25,0,0,0" Text="{Binding category}"/> </StackPanel> <TextBlock Foreground="Black" Width="270" TextWrapping="Wrap" Text="{Binding title}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </DockPanel> </Grid></Window>
Note the addition of xmlns:viewmodel="clr-namespace:SimpleDatabinding". SimpleDatabinding was the name of the project. It's used to locate the view model in the datacontext below.
The Window.DataContext binds the WPF page to the view model. I called my class MainWindowViewModel (see below). This will automatically create an instance of the view model and bind it to the window.
I introduced a button to click. It's bound to a command FillListCommand. I'll define that in the view model below.
I updated the ItemsSource on the ListBox to be bound to the Listing property.
Other than that, I think it's the same.
class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public List Listing { get; set; }
public CommandHandler FillListCommand { get; set; }
public MainWindowViewModel()
{
FillListCommand = new CommandHandler(DoFillList);
}
public void DoFillList()
{
Listing = ListMaker.getListing();
ProperyHasChanged("Listing");
}
private void ProperyHasChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Finally in the viewmodel class, I implemented the INotifyPropertyChanged interface. This is the mechanism to notify the UI that a value on your view model has changed. In most implementations, this is wrapped in some sort of ViewModel base class but I left it in so you could see it.
As above, I converted the Listing variable to a public property (get;set;) so it could be accessed through the binding.
I created a CommandHandler property called FillListCommand. This uses the class above. The button is bound to this variable. The constructor of the view model initializes and points it to the function to be called when the button is clicked.
Finally, in the DoFillList function, I initialize Listing as you had it but I also use the notification to let the UI know it's changed.
Sorry about all the writing. Hope this is somewhat helpful. I don't think it's too different from what you had.
Don't forget to decorate your data members and service methods with the appropriate tags.
These short videos are great for learning WCF:
http://channel9.msdn.com/Shows/Endpoint?sort=rating#tab_sortBy_rating
There were only 2 problems with my code, which I found:
The properties were set as private in ListingItem, which Henk
Holterman caught (+1ed)
I wasn't setting ItemSource on the list anywhere.
I didn't need to do any of the other stuff Peter Trenery mentioned at all.