MVVM Add Sort Description - c#

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.

Related

Recognizing checkbox created by a template in a fired event

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.

How to share Database within pages in Windows Phone 8?

I am creating a to do list application. At the moment I want to add a new to do list from todolistPage.xaml and after adding, I want to take the data to be able to view in the MainPage.xaml . I am able to view it from the todolistPage but not sure how to bring it to to another page. Hope to have some help. Thanks.
Below are my codes
MainPage.xaml.cs
namespace PivotApp3
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
// Set the data context of the listbox control to the sample data
DataContext = App.ViewModel;
// Sample code to localize the ApplicationBar
//BuildLocalizedApplicationBar();
}
// Load data for the ViewModel Items
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
private void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var si = mLongListSelector.SelectedItem as PivotApp3.ViewModels.ItemViewModel;
if (mLongListSelector.SelectedItem == null)
return;
if (si.LineOne.Equals("+ To Do List"))
NavigationService.Navigate(new Uri("/todolistPage.xaml", UriKind.Relative));
else if (si.LineOne.Equals("+ Reminder"))
NavigationService.Navigate(new Uri("/reminderPage.xaml", UriKind.Relative));
// Reset selected item to null (no selection)
mLongListSelector.SelectedItem = null;
}
}
MainPage.xaml
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<!-- LOCALIZATION NOTE:
To localize the displayed strings copy their values to appropriately named
keys in the app's neutral language resource file (AppResources.resx) then
replace the hard-coded text value between the attributes' quotation marks
with the binding clause whose path points to that string name.
For example:
Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"
This binding points to the template's string resource named "ApplicationTitle".
Adding supported languages in the Project Properties tab will create a
new resx file per language that can carry the translated values of your
UI strings. The binding in these examples will cause the value of the
attributes to be drawn from the .resx file that matches the
CurrentUICulture of the app at run time.
-->
<!--Pivot Control-->
<phone:Pivot Title="DAILY ROUTINE">
<!--Pivot item one-->
<phone:PivotItem Header="activity">
<!--Double line list with text wrapping-->
<phone:LongListSelector x:Name="mLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="LongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</phone:PivotItem>
<!--Pivot item two-->
<phone:PivotItem Header="today">
</phone:PivotItem>
</phone:Pivot>
<!--Uncomment to see an alignment grid to help ensure your controls are
aligned on common boundaries. The image has a top margin of -32px to
account for the System Tray. Set this to 0 (or remove the margin altogether)
if the System Tray is hidden.
Before shipping remove this XAML and the image itself.-->
<!--<Image Source="/Assets/AlignmentGrid.png" VerticalAlignment="Top" Height="800" Width="480" Margin="0,-32,0,0" Grid.Row="0" IsHitTestVisible="False" />-->
</Grid>
todolistPage.xaml.cs
namespace PivotApp3
{
public partial class todolistPage : PhoneApplicationPage, INotifyPropertyChanged
{
// Data context for the local database
private ToDoDataContext toDoDB;
// Define an observable collection property that controls can bind to.
private ObservableCollection<ToDoItem> _toDoItems;
public ObservableCollection<ToDoItem> ToDoItems
{
get
{
return _toDoItems;
}
set
{
if (_toDoItems != value)
{
_toDoItems = value;
NotifyPropertyChanged("ToDoItems");
}
}
}
//constructor
public todolistPage()
{
InitializeComponent();
// Connect to the database and instantiate data context.
toDoDB = new ToDoDataContext(ToDoDataContext.DBConnectionString);
// Data context and observable collection are children of the main page.
this.DataContext = this;
}
private void deleteTaskButton_Click(object sender, RoutedEventArgs e)
{
// Cast parameter as a button.
var button = sender as Button;
if (button != null)
{
// Get a handle for the to-do item bound to the button.
ToDoItem toDoForDelete = button.DataContext as ToDoItem;
// Remove the to-do item from the observable collection.
ToDoItems.Remove(toDoForDelete);
// Remove the to-do item from the local database.
toDoDB.ToDoItems.DeleteOnSubmit(toDoForDelete);
// Save changes to the database.
toDoDB.SubmitChanges();
// Put the focus back to the main page.
this.Focus();
}
}
private void newToDoTextBox_GotFocus(object sender, RoutedEventArgs e)
{
// Clear the text box when it gets focus.
newToDoTextBox.Text = String.Empty;
}
private void newToDoAddButton_Click(object sender, RoutedEventArgs e)
{
// Create a new to-do item based on the text box.
ToDoItem newToDo = new ToDoItem { ItemName = newToDoTextBox.Text };
// Add a to-do item to the observable collection.
ToDoItems.Add(newToDo);
// Add a to-do item to the local database.
toDoDB.ToDoItems.InsertOnSubmit(newToDo);
}
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
// Call the base method.
base.OnNavigatedFrom(e);
// Save changes to the database.
toDoDB.SubmitChanges();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// Define the query to gather all of the to-do items.
var toDoItemsInDB = from ToDoItem todo in toDoDB.ToDoItems
select todo;
// Execute the query and place the results into a collection.
ToDoItems = new ObservableCollection<ToDoItem>(toDoItemsInDB);
// Call the base method.
base.OnNavigatedTo(e);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the app that a property has changed.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public class ToDoDataContext : DataContext
{
// Specify the connection string as a static, used in main page and app.xaml.
public static string DBConnectionString = "Data Source=isostore:/ToDo.sdf";
// Pass the connection string to the base class.
public ToDoDataContext(string connectionString)
: base(connectionString)
{ }
// Specify a single table for the to-do items.
public Table<ToDoItem> ToDoItems;
}
[Table]
public class ToDoItem : INotifyPropertyChanged, INotifyPropertyChanging
{
// Define ID: private field, public property and database column.
private int _toDoItemId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int ToDoItemId
{
get
{
return _toDoItemId;
}
set
{
if (_toDoItemId != value)
{
NotifyPropertyChanging("ToDoItemId");
_toDoItemId = value;
NotifyPropertyChanged("ToDoItemId");
}
}
}
// Define item name: private field, public property and database column.
private string _itemName;
[Column]
public string ItemName
{
get
{
return _itemName;
}
set
{
if (_itemName != value)
{
NotifyPropertyChanging("ItemName");
_itemName = value;
NotifyPropertyChanged("ItemName");
}
}
}
// Define completion value: private field, public property and database column.
private bool _isComplete;
[Column]
public bool IsComplete
{
get
{
return _isComplete;
}
set
{
if (_isComplete != value)
{
NotifyPropertyChanging("IsComplete");
_isComplete = value;
NotifyPropertyChanged("IsComplete");
}
}
}
// Version column aids update performance.
[Column(IsVersion = true)]
private Binary _version;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the page that a data context property changed
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Used to notify the data context that a data context property is about to change
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
todolistPage.xaml
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="TO DO LIST" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="add" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!-- Bind the list box to the observable collection. -->
<ListBox x:Name="toDoItemsListBox" ItemsSource="{Binding ToDoItems}"
Grid.Row="1" Margin="12,0,28,210" Width="440">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Width="440">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<CheckBox
IsChecked="{Binding IsComplete, Mode=TwoWay}"
Grid.Column="0"
VerticalAlignment="Center"/>
<TextBlock
Text="{Binding ItemName}"
FontSize="{StaticResource PhoneFontSizeLarge}"
Grid.Column="1"
VerticalAlignment="Center"/>
<Button
Grid.Column="2"
x:Name="deleteTaskButton"
BorderThickness="0"
Margin="0"
Click="deleteTaskButton_Click">
<Image Source="appbar.delete.rest.png"/>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="2" Margin="12,465,12,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<TextBox
x:Name="newToDoTextBox"
Grid.Column="0"
Text="add new task"
FontFamily="{StaticResource PhoneFontFamilyLight}"
GotFocus="newToDoTextBox_GotFocus" Margin="0,-65,0,104" Grid.ColumnSpan="2"/>
<Button
Content="add"
x:Name="newToDoAddButton"
Click="newToDoAddButton_Click" Margin="150,43,130,10" Grid.ColumnSpan="2"/>
</Grid>
</Grid>
Database Created
using (ToDoDataContext db = new ToDoDataContext(ToDoDataContext.DBConnectionString))
{
if (db.DatabaseExists() == false)
{
//Create the database
db.CreateDatabase();
}
}
Put your database into App.xaml.cs:
// Data context for the local database
public ToDoDataContext toDoDB;
add this code to App.xaml.cs:
public new static App Current
{
get
{
return (App)Application.Current;
}
}
Then you can access your database everywhere by using:
App.Current.toDoDB...
You can create class to manipulate data in sql and make it avaiable from App.xaml.cs
Here is sample code
ToDoDataViewModel class:
public class ToDoDataViewModel : INotifyPropertyChanged
{
ToDoDataContext db;
public ToDoDataViewModel(string connectionString)
{
db = new ToDoDataContext(connectionString);
}
private ObservableCollection<ToDoItem> _toDoItems;
public ObservableCollection<ToDoItem> ToDoItems
{
get { return this._toDoItems; }
set
{
this._toDoItems = value;
NotifyPropertyChanged("ToDoItems");
}
}
public void LoadCollectionsFromDatabase()
{
var toDos = from todo in db.ToDoItems
select todo;
_toDoItems = new ObservableCollection<ToDoItem>(toDos);
}
public void InsertToDoItem(ToDoItem item)
{
db.ToDoItems.InsertOnSubmit(item);
_toDoItems.Add(item);
db.SubmitChanges();
}
public void DeleteToDoItem(ToDoItem item)
{
db.ToDoItems.DeleteOnSubmit(item);
_toDoItems.Remove(item);
db.SubmitChanges();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In App.xaml.cs:
public partial class App : Application
{
private static ToDoDataViewModel _viewModel;
public static ToDoDataViewModel ViewModel
{
get { return _viewModel; }
}
//other methods of App
public App()
{
//place this code at the and of the contructor
CreateDb();
}
private void CreateDb()
{
using(var db=new ToDoDataContext(ToDoDataContext.DBConnectionString))
{
if(!db.DatabaseExists())
{
db.CreateDatabase();
}
}
_viewModel=new ToDoDataViewModel(ToDoDataContext.DBConnectionString);
_viewModel.LoadCollectionsFromDatabase();
}
}
And place this code in the constructors of your pages:
this.DataContext=App.ViewModel;
This way you separated your database logic from application logic
Now you can update your newToDoAddButton_Click method as following:
private void newToDoAddButton_Click(object sender, RoutedEventArgs e)
{
// Create a new to-do item based on the text box.
ToDoItem newToDo = new ToDoItem { ItemName = newToDoTextBox.Text };
//Add to-do item to the local database
App.ViewModel.InsertToDoItem(newToDo);
this.Focus();
}
And your deleteTaskButton_Click method:
private void deleteTaskButton_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button != null)
{
// Get a handle for the to-do item bound to the button.
ToDoItem toDoForDelete = button.DataContext as ToDoItem;
// Remove the to-do item from the local database.
App.ViewModel.DeleteToDoItem(toDoForDelete);
this.Focus();
}
}

How could I add combox and other items in a listbox?

I need to create an UI which allows me to select entries from one list box and add it to another listbox at the run time. Now, the listbox1 may contain combo box and checkbox as the items.
For example, if I add a combo box labelled Quarter with values "Q1, Q2, Q3, Q4" as an item in listbox1 and select the entry Q1 in it, and click on the "Add" button, it should be added to listbox2. Vice versa should also be possible. This should be possible at the run time. How could I add combo box and checkbox as an item to the listbox? Also, please suggest if for the add-remove buttons, the code I've is correct.
private void MoveListBoxItems(ListBox source, ListBox destination)
{
ListBox.SelectedObjectCollection sourceItems = source.SelectedItems;
foreach (var item in sourceItems)
{
destination.Items.Add(item);
}
while (source.SelectedItems.Count > 0)
{
source.Items.Remove(source.SelectedItems[0]);
}
}
private void button1_Click(object sender, EventArgs e)
{
MoveListBoxItems(listBox1, listBox2);
}
private void button2_Click(object sender, EventArgs e)
{
MoveListBoxItems(listBox2, listBox1);
}
This is a WPF solution to your need. I am posting it because you told me it could be useful for you. It largely surpasses anything you can ever hope to achieve in winforms, which is a very limited and outdated technology.
This is how it looks in my screen:
I am using some simple ViewModels to represent the data:
ListItemViewModel (the "base" one):
public class ListItemViewModel: ViewModelBase
{
private string _displayName;
public string DisplayName
{
get { return _displayName; }
set
{
_displayName = value;
NotifyPropertyChange(() => DisplayName);
}
}
}
BoolListItemViewModel (for CheckBoxes):
public class BoolListItemViewModel: ListItemViewModel
{
private bool _value;
public bool Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged(() => Value);
}
}
}
SelectableListItemViewModel (for ComboBoxes):
public class SelectableListItemViewModel: ListItemViewModel
{
private ObservableCollection<ListItemViewModel> _itemsSource;
public ObservableCollection<ListItemViewModel> ItemsSource
{
get { return _itemsSource ?? (_itemsSource = new ObservableCollection<ListItemViewModel>()); }
}
private ListItemViewModel _selectedItem;
public ListItemViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChange(() => SelectedItem);
}
}
}
This is the "Main" ViewModel, which holds the 2 lists and the Commands (the Button actions)
public class ListBoxSampleViewModel: ViewModelBase
{
private ObservableCollection<ListItemViewModel> _leftItems;
public ObservableCollection<ListItemViewModel> LeftItems
{
get { return _leftItems ?? (_leftItems = new ObservableCollection<ListItemViewModel>()); }
}
private ObservableCollection<ListItemViewModel> _rightItems;
public ObservableCollection<ListItemViewModel> RightItems
{
get { return _rightItems ?? (_rightItems = new ObservableCollection<ListItemViewModel>()); }
}
private DelegateCommand<ListItemViewModel> _moveToRightCommand;
public DelegateCommand<ListItemViewModel> MoveToRightCommand
{
get { return _moveToRightCommand ?? (_moveToRightCommand = new DelegateCommand<ListItemViewModel>(MoveToRight)); }
}
private void MoveToRight(ListItemViewModel item)
{
if (item != null)
{
LeftItems.Remove(item);
RightItems.Add(item);
}
}
private DelegateCommand<ListItemViewModel> _moveToLeftCommand;
public DelegateCommand<ListItemViewModel> MoveToLeftCommand
{
get { return _moveToLeftCommand ?? (_moveToLeftCommand = new DelegateCommand<ListItemViewModel>(MoveToLeft)); }
}
private void MoveToLeft(ListItemViewModel item)
{
if (item != null)
{
RightItems.Remove(item);
LeftItems.Add(item);
}
}
}
This is the entire XAML for the Window:
<Window x:Class="WpfApplication4.Window14"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="Window14" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ListItemViewModel}">
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:BoolListItemViewModel}">
<CheckBox Content="{Binding DisplayName}" IsChecked="{Binding Value}" HorizontalAlignment="Left"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SelectableListItemViewModel}">
<ComboBox ItemsSource="{Binding ItemsSource}" SelectedItem="{Binding SelectedItem}"
HorizontalAlignment="Stretch" MinWidth="100"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding LeftItems}"
x:Name="LeftList"/>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<Button Content="Move to Right"
Command="{Binding MoveToRightCommand}"
CommandParameter="{Binding SelectedItem,ElementName=LeftList}"/>
<Button Content="Move to Left"
Command="{Binding MoveToLeftCommand}"
CommandParameter="{Binding SelectedItem,ElementName=RightList}"/>
</StackPanel>
<ListBox ItemsSource="{Binding RightItems}"
Grid.Column="2" x:Name="RightList"/>
</Grid>
</Window>
and finally, this is the Window Code-behind, which only initializes the ViewModel with some items:
public partial class Window14 : Window
{
public Window14()
{
InitializeComponent();
DataContext = new ListBoxSampleViewModel()
{
LeftItems =
{
new ListItemViewModel(){DisplayName = "Item1"},
new BoolListItemViewModel() {DisplayName = "Check Item 2", Value = true},
new SelectableListItemViewModel()
{
ItemsSource =
{
new ListItemViewModel() {DisplayName = "Combo Item 1"},
new BoolListItemViewModel() {DisplayName = "Check inside Combo"},
new SelectableListItemViewModel()
{
ItemsSource =
{
new ListItemViewModel() {DisplayName = "Wow, this is awesome"},
new BoolListItemViewModel() {DisplayName = "Another CheckBox"}
}
}
}
}
}
};
}
}
At first glance, this might seem like a LOT of code... but if you take 2 seconds to analyze it... Its just "simple, simple properties and INotifyPropertyChanged. That's how you program in WPF.
I'm talking about a completely different paradigm from what you might be used to in winforms, but it's really worth the effort of learning it. Notice that nowhere in my code I am interacting with UI elements. I just create the ViewModel structure and let the WPF Binding System to take care of generating the UI for me, using the provided DataTemplates.
I'm using the ViewModelBase from MVVM Light and the DelegateCommand from WPFTutorial.net. You can copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself (you will also need these 2 classes from the links above)
If you need to integrate this in an existing winforms application, you will need the ElementHost

WPF ComboBox Binding works if previous record had no matching item in the ItemsSource collection and fails if it did

I have the following WPF Combobox:
<Window.Resources>
<CollectionViewSource x:Key="performanceItemsource" Source="{Binding Path=SelectedReport.Performances}" >
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Name"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
...
<ComboBox Name="cbxPlanPerf" Grid.ColumnSpan="2"
SelectedValuePath="MSDPortfolioID" DisplayMemberPath="Name"
SelectedValue="{Binding Path=PlanPerfID}"
ItemsSource="{Binding Source={StaticResource performanceItemsource}}"/>
The Source for the CollectionViewSource is:
public List<MSDExportProxy> Performances
{
get
{
if (Portfolio != null)
{
return (from a in Portfolio.Accounts where a.MSDPortfolioID != null select new MSDExportProxy(a))
.Concat<MSDExportProxy>(from g in Portfolio.Groups where g.MSDPortfolioID != null select new MSDExportProxy(g))
.Concat<MSDExportProxy>(from p in new[] { Portfolio } where p.MSDPortfolioID != null select new MSDExportProxy(p))
.ToList<MSDExportProxy>();
}
return new List<MSDExportProxy>();
}
}
The bound property PlanPerfID is a string.
I move between records using a ListBox control. The ComboBox works fine if the previous record had no items in its ComboBox.ItemsSource. If there were any items in the previous record's ComboBox.ItemsSource then the new record won't find its matching item in the ItemsSource collection. I've tried setting the ItemsSource in both XAML and the code-behind, but nothing changes this odd behavior. How can I get this darn thing to work?
Try using ICollectionViews in combination with IsSynchronizedWithCurrentItem property when handling lists / ObservableCollection in Xaml. The ICollectionView in the viewmodel can handle all the things needed, e.g. sorting, filtering, keeping track of selections and states.
Xaml:
<Window x:Class="ComboBoxBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0"
ItemsSource="{Binding Reports}"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True" />
<ComboBox Grid.Column="1"
ItemsSource="{Binding CurrentReport.Performances}"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True" />
</Grid>
</Window>
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private readonly IReportService _reportService;
private ObservableCollection<ReportViewModel> _reports = new ObservableCollection<ReportViewModel>();
private PerformanceViewModel _currentPerformance;
private ReportViewModel _currentReport;
public ObservableCollection<ReportViewModel> Reports
{
get { return _reports; }
set { _reports = value; OnPropertyChanged("Reports");}
}
public ReportViewModel CurrentReport
{
get { return _currentReport; }
set { _currentReport = value; OnPropertyChanged("CurrentReport");}
}
public PerformanceViewModel CurrentPerformance
{
get { return _currentPerformance; }
set { _currentPerformance = value; OnPropertyChanged("CurrentPerformance");}
}
public ICollectionView ReportsView { get; private set; }
public ICollectionView PerformancesView { get; private set; }
public ViewModel(IReportService reportService)
{
if (reportService == null) throw new ArgumentNullException("reportService");
_reportService = reportService;
var reports = _reportService.GetData();
Reports = new ObservableCollection<ReportViewModel>(reports);
ReportsView = CollectionViewSource.GetDefaultView(Reports);
ReportsView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
ReportsView.CurrentChanged += OnReportsChanged;
ReportsView.MoveCurrentToFirst();
}
private void OnReportsChanged(object sender, EventArgs e)
{
var selectedReport = ReportsView.CurrentItem as ReportViewModel;
if (selectedReport == null) return;
CurrentReport = selectedReport;
if(PerformancesView != null)
{
PerformancesView.CurrentChanged -= OnPerformancesChanged;
}
PerformancesView = CollectionViewSource.GetDefaultView(CurrentReport.Performances);
PerformancesView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
PerformancesView.CurrentChanged += OnPerformancesChanged;
PerformancesView.MoveCurrentToFirst();
}
private void OnPerformancesChanged(object sender, EventArgs e)
{
var selectedperformance = PerformancesView.CurrentItem as PerformanceViewModel;
if (selectedperformance == null) return;
CurrentPerformance = selectedperformance;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I found a quick and dirty solution to my problem. I just happen to have a public NotifyPropertyChanged() method on my Report entity and I discovered that if I called SelectedReport.NotifyPropertyChanged("PlanPerfID") in the Report ListBox's SelectionChanged event that it was enough of a jolt to get the ComboBox to re-evaluate and find its matching item in the ItemsSource. Yeah, it's KLUGE...
UPDATE: I also wound up needing to add SelectedReport.NotifyPropertyChanged("Performances") for some situations...
UPDATE 2: Okay, turns out the above wasn't bullet proof and I ran across a situation that broke it so I had to come up with a better workaround:
Altered the SelectedReport property in the Window's code-behind, adding a private flag (_settingCombos) to keep the Binding from screwing up the bound values until the dust has settled from changin the ItemSource:
private bool _settingCombos = false;
private Report _SelectedReport;
public Report SelectedReport
{
get { return _SelectedReport; }
set
{
_settingCombos = true;
_SelectedReport = value;
NotifyPropertyChanged("SelectedReport");
}
}
Created a proxy to bind to in the Window code-behind that will refuse to update the property's value if the _settingCombos flag is true:
public string PlanPerfID_Proxy
{
get { return SelectedReport.PlanPerfID; }
set
{
if (!_settingCombos)
{
SelectedReport.PlanPerfID = value;
NotifyPropertyChanged("PlanPerfID_Proxy");
}
}
}
Added an extra Notification in the Report ListBox's SelectionChanged event along with code to reset the _settingCombos flag back to false:
private void lbxReports_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//KLUGE: Couldn't get the ComboBoxes associated with these properties to work right
//this forces them to re-evaluate after the Report has loaded
if (SelectedReport != null)
{
NotifyPropertyChanged("PlanPerfID_Proxy");
_settingCombos = false;
}
}
Bound the ComboBox to the PlanPerfID_Proxy property (instead of directly to the SelectedReport.PlanPerfID property.
Wow, what a hassle! I think that this is simply a case of .NET's binding logic getting confused by the dynamic nature of the ComboBox.ItemSource, but this seems to have fixed it. Hope it helps someone else.

How to bind usercontrols in xaml?

I have MainWindow containing a datagrid and a "filter panel". The filter panel can change by a user input(button click). I try to achieve it with databinding. The problem that Im facing is the filter panel(which is a user control) is not loaded or refreshed.
Mainwindow xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250*" />
<ColumnDefinition Width="253*" />
</Grid.ColumnDefinitions>
<DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Margin="23,28,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="200" ItemsSource="{Binding OverviewableItems}" />
<UserControl Grid.Column="1" Content="{Binding UserControl}" DataContext="{Binding}" Grid.ColumnSpan="2" />
<Button Content="PersonFilter" Height="23" HorizontalAlignment="Left" Margin="23,268,0,0" Name="buttonPersonFilter" VerticalAlignment="Top" Width="75" Click="buttonPersonFilter_Click" />
<Button Content="ProjectFilter" Height="23" HorizontalAlignment="Left" Margin="132,268,0,0" Name="buttonProjectFilter" VerticalAlignment="Top" Width="75" Click="buttonProjectFilter_Click" />
</Grid>
code behind:
private ViewModel _viewModel;
public MainWindow()
{
_viewModel = new ViewModel(new DataProvider());
DataContext = _viewModel;
_viewModel.PropertyChanged += _viewModel.SetFilterType;
InitializeComponent();
}
private void buttonProjectFilter_Click(object sender, RoutedEventArgs e)
{
_viewModel.OverviewType = OverviewType.Project;
}
private void buttonPersonFilter_Click(object sender, RoutedEventArgs e)
{
_viewModel.OverviewType = OverviewType.Person;
}
First user control:
<Grid>
<DatePicker Grid.Column="1" Grid.Row="1" Height="25" HorizontalAlignment="Left" Margin="19,18,0,0" Name="datePickerFundingTo" VerticalAlignment="Top" Width="115" Text="{Binding ElementName=ProjectFilter, Path=FundingTo}" />
</Grid>
code behind for this user control is only this:
public DateTime FundingTo { get; set; }
public ProjectFilter()
{
FundingTo = DateTime.Now;
InitializeComponent();
}
Other user control: just simply contains a TextBox and a Button, for the sake of simplicity I didnt add any code behind to it.
ViewModel of the MainWindow:
public class ViewModel : INotifyPropertyChanged
{
private UserControl _userControl;
public UserControl UserControl
{
get { return _userControl; }
set
{
if (_userControl == value)
{
return;
}
OnPropertyChanged("UserControl");
_userControl = value;
}
}
private OverviewType _overviewType = OverviewType.None;
public OverviewType OverviewType
{
get { return _overviewType; }
set
{
if (_overviewType == value)
{
return;
}
OnPropertyChanged("OverviewType");
_overviewType = value;
}
}
private ObservableCollection<IOverviewItem> _overviewableItems;
public ObservableCollection<IOverviewItem> OverviewableItems
{
get { return _overviewableItems; }
set
{
if (_overviewableItems == value)
{
return;
}
_overviewableItems = value;
}
}
private readonly DataProvider _dataProvider;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(DataProvider dataProvider)
{
_dataProvider = dataProvider;
}
public void SetFilterType(object sender, EventArgs eventArgs)
{
switch (_overviewType)
{
case OverviewType.Project:
_userControl = new ProjectFilter();
break;
case OverviewType.Person:
_userControl = new PersonFilter();
break;
}
}
public void OnPropertyChanged(string name)
{
if (PropertyChanged == null)
return;
var eventArgs = new PropertyChangedEventArgs(name);
PropertyChanged(this, eventArgs);
}
}
plus I have an enum OverviewType with None,Project,Person values.
The property changed event fired properly, but the user control is not refreshed. Could anyone enlight me, where is the flaw in my solution?
And the other question I have, how can I communicate from the usercontrols to the mainwindow viewmodel? Forex: the datagrid should be changed according to its filter.
Any help would be greatly appreciated. Cheers!
There are different problems here.
As Clemens said, you must fire your event after the value is updated. But it's not the main issue here.
Second problem: you are affecting your new usercontrol to the private member, so you're totally bypassing your property.
Replace
_userControl = new ProjectFilter();
by
this.UserControl = new ProjectFilter();
Third problem, which is not directly related to your question but actually is your biggest problem: you have an architecture design issue. You're exposing in your viewmodel a UserControl, which is an anti-pattern. Your viewmodel must not know anything about the view, so it must NOT have any reference to the controls inside the view. Instead of the binding you wrote, you could fire an event from the viewmodel and add an event handler in your view so it's your view that updates the usercontrol.
Try to fire the PropertyChanged after changing a property's backing field:
public UserControl UserControl
{
get { return _userControl; }
set
{
if (_userControl != value)
{
_userControl = value; // first
OnPropertyChanged("UserControl"); // second
}
}
}
Similar for OverviewType.

Categories

Resources