I am developing a application following WPF MVVM pattern, There I need to fill a datagrid with data when button click event. So My view model related to grid also get filled with data but it doesn't appear in the grid in screen. What I have done wrong here? Any advice will be much appreciated.
Author-Model Class
public class Author
{
private int id;
private string name;
private string bookTitle;
private bool isMVP;
public Author(int ID, string Name, string BookTitle, bool IsMVP)
{
this.id = ID;
this.name = Name;
this.bookTitle = BookTitle;
this.isMVP = IsMVP;
}
public int ID
{
get { return id; }
set { id = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public string BookTitle
{
get { return bookTitle; }
set { bookTitle = value; }
}
public bool IsMVP
{
get { return isMVP; }
set { isMVP = value; }
}
}
AuthorViewModel
public class AuthorViewModel: ObservableCollection<Author>
{
public AuthorViewModel():base()
{
Add(new Author
(
1,
"Cather",
"Graphics Programming",
true
));
Add(new Author
(
2,
"Mathew Cochran",
"LINQ in Vista",
true
));
Add(new Author
(
3,
"Mike Gold",
"Programming in Vista",
true
));
}
}
MainWindowViewModel
public class MainWindowViewModel
{
public ICommand LoadGridCommand { get; set; }
public MainWindowViewModel()
{
LoadGridCommand = new RelayCommand(LoadGrid,null);
}
private void LoadGrid(object parameter)
{
AuthorViewModel authorVM = new AuthorViewModel();
}
}
MainWindow.xaml
<Window x:Class="CustomCtrldemo.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:CustomCtrldemo"
xmlns:vm="clr-namespace:CustomCtrldemo.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<vm:MainWindowViewModel x:Key="MainWindowVM"/>
<vm:AuthorViewModel x:Key="AuthorVM"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource MainWindowVM }}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox Name="TxtFilterGrid" Grid.Row="0" Grid.Column="0" Width="100" Height="20" />
<Button Grid.Row="1" Grid.Column="0" Content="Load Grid" Width="100" Height="20" Command="{Binding LoadGridCommand}" />
<Grid x:Name="gridAuthors" Grid.Column="1" Grid.RowSpan="4" DataContext="{Binding Source={StaticResource AuthorVM} }" >
<DataGrid x:Name="dgAuthors" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Path=ID}" />
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" />
<DataGridTextColumn Header="Book Title" Binding="{Binding Path=BookTitle}" />
<DataGridCheckBoxColumn Header="MVP Enabled" Binding="{Binding Path=IsMVP}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
<Button Grid.Row="2" Grid.Column="0" Content="Load Control" Width="100" Height="20"/>
<Button Grid.Row="3" Grid.Column="0" Content="Set Content" Width="100" Height="20"/>
<TextBox Name="txtbox1" Grid.Row="4" Grid.Column="0" Width="100" Height="20" />
<TextBox Name="txtUniqueId" Grid.Row="4" Grid.Column="1" Width="100" Height="20"/>
<local:LoadControl />
<WrapPanel HorizontalAlignment="center" Grid.Row="6" Grid.Column="1" >
<Button Content="OK" Width="100" Height="20"/>
<Button Content="Cancel" Width="100" Height="20"/>
</WrapPanel>
</Grid>
</Window>
Your separate AuthorViewModel doesn't make sense. You should instead have a Authors property in the main view model:
public class MainWindowViewModel
{
public ICommand LoadGridCommand { get; }
public ObservableCollection<Author> Authors { get; }
= new ObservableCollection<Author>();
public MainWindowViewModel()
{
LoadGridCommand = new RelayCommand(LoadGrid, null);
}
private void LoadGrid(object parameter)
{
// Authors.Clear();
// Authors.Add(...);
}
}
You should assign an instance of the main view model to the window's DataContext and bind like this:
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<DataGrid ItemsSource="{Binding Authors}" ...>
Related
I've a ListView whose data I select and send to a DataGrid. I am having trouble with the quantity column of the DataGrid which I would want to calculate how many times a ListView item has been added to the said DataGrid (I'm currently displaying a success message when the same item is selected). I would also want to calculate the price and the quantity and display them on one column named 'price' the DataGrid.
Here is the Datagrid
<ListView x:Name="ItemGridView" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" PreviewMouseDoubleClick="ItemGridView_PreviewMouseDoubleClick">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Source="{Binding ItemImage}" Width="225" Height="157" Stretch="UniformToFill" StretchDirection="DownOnly" />
<StackPanel Margin="0,100,0,0">
<Border Margin="-0,-7,0,0" Height="63" Width="225" Background="{x:Null}" BorderBrush="{x:Null}" BorderThickness="0">
<TextBlock Margin="8" FontWeight="Heavy" Foreground="White" FontSize="16" Text="{Binding ItemName}"/>
</Border>
<TextBlock Margin="15,-28,0,0" FontSize="15" Text="{Binding SellingPrice}" Foreground="White"/>
</StackPanel>
</Grid>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
The DataGrid to which the data is sent looks like this:
<DataGrid x:Name="DGItems" ItemsSource="{Binding}" VerticalAlignment="Top" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Grid.Row="0" MinHeight="350" MaxHeight="350" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" CanUserSortColumns="True" CanUserAddRows="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn MinWidth="3" Header="#" Width="Auto" Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Converter={global:RowToIndexConverter}}" />
<DataGridTextColumn Header="Items" Binding="{Binding ItemName}" />
<DataGridTextColumn Header="Cost" Binding="{Binding SellingPrice}" />
<DataGridTextColumn Header="Qty" />
</DataGrid.Columns>
</DataGrid>
The code behind to send the datan following a ListView double click event is as below:
private void ItemGridView_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var selectedItem = ItemGridView.SelectedItem;
if (!DGItems.Items.Contains(selectedItem))
{
DGItems.Items.Add(selectedItem);
}
else
{
utilityMethods.InformationMessage("Attempted to add item successfully");
}
}
I've included screenshots to preview how the application looks just to put the question in context.
Here's a working sample. I made slight modifications to your xaml and bindings to show how it can be done. Whenever you double-click an item in the ListView, it will update both the Quantity and Total columns in the DataGrid.
MyItem.cs: This is a simple model to replicate your food item
namespace UpdateQuantityColumnTest
{
public class MyItem
{
public string ItemName { get; set; }
public double SellingPrice { get; set; }
}
}
ListViewItemViewModel.cs: This is a view model representation of your MyItem for the ListView
namespace UpdateQuantityColumnTest
{
public class ListViewItemViewModel : ViewModelBase
{
public ListViewItemViewModel(MyItem model)
{
this.Model = model;
}
public MyItem Model { get; private set; }
public string ItemName { get => this.Model.ItemName; set { this.Model.ItemName = value; OnPropertyChanged(); } }
public string SellingPrice { get => this.Model.SellingPrice.ToString("c"); }
}
}
DGItemViewModel: This is a slightly different view model representation of your MyItem for the DataGrid, but includes a Quantity and a TotalPrice
namespace UpdateQuantityColumnTest
{
public class DGItemViewModel : ViewModelBase
{
private int quantity;
public DGItemViewModel(MyItem model)
{
this.Model = model;
this.quantity = 1; // always start at 1
}
public MyItem Model { get; private set; }
public string ItemName { get => this.Model.ItemName; set { this.Model.ItemName = value; OnPropertyChanged(); } }
public string SellingPrice { get => this.Model.SellingPrice.ToString("c"); }
public int Quantity { get => this.quantity; set { this.quantity = value; OnPropertyChanged(); OnPropertyChanged(nameof(TotalPrice)); } }
public string TotalPrice { get => (this.Model.SellingPrice * this.Quantity).ToString("c"); }
}
}
ViewModelBase.cs: This is the base class that simply handles the INotifyPropertyChanged to update the UI whenever one of the property values change in your view model
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace UpdateQuantityColumnTest
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainWindow.xaml: I made slight changes, mainly to your bindings so that my sample could work
<Window x:Class="UpdateQuantityColumnTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView x:Name="ItemGridView" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" PreviewMouseDoubleClick="ItemGridView_PreviewMouseDoubleClick">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Source="{Binding ItemImage}" Width="225" Height="157" Stretch="UniformToFill" StretchDirection="DownOnly" />
<StackPanel Margin="0,100,0,0">
<Border Margin="-0,-7,0,0" Height="63" Width="225" Background="{x:Null}" BorderBrush="{x:Null}" BorderThickness="0">
<TextBlock Margin="8" FontWeight="Heavy" Foreground="White" FontSize="16" Text="{Binding ItemName}"/>
</Border>
<TextBlock Margin="15,-28,0,0" FontSize="15" Text="{Binding SellingPrice}" Foreground="White"/>
</StackPanel>
</Grid>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<DataGrid x:Name="DGItems" ItemsSource="{Binding CheckoutItems}" VerticalAlignment="Top" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Grid.Row="1" MinHeight="350" MaxHeight="350" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" CanUserSortColumns="True" CanUserAddRows="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Items" Binding="{Binding ItemName}" />
<DataGridTextColumn Header="Cost" Binding="{Binding SellingPrice}" />
<DataGridTextColumn Header="Qty" Binding="{Binding Quantity}"/>
<DataGridTextColumn Header="Total" Binding="{Binding TotalPrice}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
MainWindow.xaml.cs: The code-behind that does all the work
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
namespace UpdateQuantityColumnTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<ListViewItemViewModel> items;
private ObservableCollection<DGItemViewModel> checkoutItems;
public MainWindow()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.DataContext = this;
}
public ObservableCollection<ListViewItemViewModel> Items
{
get
{
if (this.items == null)
this.items = new ObservableCollection<ListViewItemViewModel>();
return this.items;
}
}
public ObservableCollection<DGItemViewModel> CheckoutItems
{
get
{
if (this.checkoutItems == null)
this.checkoutItems = new ObservableCollection<DGItemViewModel>();
return this.checkoutItems;
}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Populate with dummy data
this.Items.Add(new ListViewItemViewModel(new MyItem() { ItemName = "Beef Steak", SellingPrice = 1000 }));
this.Items.Add(new ListViewItemViewModel(new MyItem() { ItemName = "Bacon Brie", SellingPrice = 1200 }));
this.Items.Add(new ListViewItemViewModel(new MyItem() { ItemName = "Bread and Sausage", SellingPrice = 700 }));
}
private void ItemGridView_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var selectedItem = ItemGridView.SelectedItem as ListViewItemViewModel;
var checkoutItem = this.CheckoutItems.SingleOrDefault(o => o.ItemName == selectedItem.ItemName);
if (checkoutItem == null)
{
this.CheckoutItems.Add(new DGItemViewModel(selectedItem.Model));
}
else
{
//utilityMethods.InformationMessage("Attempted to add item successfully");
checkoutItem.Quantity++;
}
}
}
}
If the items in the ItemsSource = "{Binding Items}" collection have an INotifyPropertyChanged implementation and notify about the change of their properties, then you can solve this problem with minimal changes.
Add an int Qty property to the element class and change your method:
if (!DGItems.Items.Contains(selectedItem))
{
DGItems.Items.Add(selectedItem);
selectedItem.Qty = 1;
}
else
{
selectedItem.Qty++;
utilityMethods.InformationMessage("Attempted to add item successfully");
}
And add a binding to this property in the column:
<DataGrid.Columns>
<DataGridTextColumn MinWidth="3" Header="#" Width="Auto" Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Converter={global:RowToIndexConverter}}" />
<DataGridTextColumn Header="Items" Binding="{Binding ItemName}" />
<DataGridTextColumn Header="Cost" Binding="{Binding SellingPrice}" />
<DataGridTextColumn Header="Qty" Binding="{Binding Qty}" />
</DataGrid.Columns>
I've been digging back into some projects in WPF, and come across a hurdle that I haven't been able to find a directly related solution for.
Essentially I want to filter a child property of a SelectedItem dynamically (via text entered in the filter box, something along the lines of .Contains(filter)). The UI displays correctly in the sample project, but after attempting to implement solutions from every hit possible on SO or otherwise, I've come up blank, or making serious compromises to the MVVM pattern.
ParentItem:
public class ParentItem
{
public string Name { get; set; }
public List<string> ChildItems { get; set; }
public DateTime CreatedOn { get; set; }
public bool IsActive { get; set; }
public ParentItemStatus Status { get; set; }
}
public enum ParentItemStatus
{
Status_One,
Status_Two
}
ViewModel:
public class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<ParentItem> ParentItems { get; set; }
public MainWindowViewModel()
{
ParentItems = new ObservableCollection<ParentItem>();
LoadDummyParentItems();
}
private ICommand _filterChildrenCommand;
public ICommand FilterChildrenCommand => _filterChildrenCommand ?? (_filterChildrenCommand = new RelayCommand(param => FilterChildren((string)param), param => CanFilterChildren((string)param)));
private bool CanFilterChildren(string filter)
{
//TODO: Check for selected item in real life.
return filter.Length > 0;
}
private void FilterChildren(string filter)
{
//TODO: Filter?
}
private void LoadDummyParentItems()
{
for (var i = 0; i < 20; i++)
{
ParentItems.Add(new ParentItem()
{
Name = $"Parent Item {i}",
CreatedOn = DateTime.Now.AddHours(i),
IsActive = i % 2 == 0 ? true : false,
Status = i % 2 == 0 ? ParentItemStatus.Status_Two : ParentItemStatus.Status_One,
ChildItems = new List<string>() { $"Child one_{i}", $"Child two_{i}", $"Child three_{i}", $"Child four_{i}" }
});
}
}
}
MainWindow:
<Window x:Class="FilteringDemo.Views.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:FilteringDemo.Views"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<CollectionViewSource x:Key="ChildItemsViewSource" Source="{Binding ElementName=ItemList, Path=SelectedItem.ChildItems}" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".25*"/>
<ColumnDefinition Width=".75*"/>
</Grid.ColumnDefinitions>
<ListView x:Name="ItemList" Grid.Column="0" Margin="2" ItemsSource="{Binding ParentItems}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ElementName=ItemList, Path=SelectedItem.Name}" Margin="2"/>
<TextBlock Grid.Column="1" Text="{Binding ElementName=ItemList, Path=SelectedItem.CreatedOn}" Margin="2"/>
<TextBlock Grid.Column="2" Text="{Binding ElementName=ItemList, Path=SelectedItem.IsActive}" Margin="2"/>
<TextBlock Grid.Column="3" Text="{Binding ElementName=ItemList, Path=SelectedItem.Status}" Margin="2"/>
</Grid>
<ListView Grid.Row="1" Margin="2" ItemsSource="{Binding Source={StaticResource ChildItemsViewSource}}" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Contains:" Margin="2" VerticalAlignment="Center"/>
<TextBox x:Name="ChildFilterInput" Grid.Column="1" Margin="2" />
<Button Grid.Column="2" Content="Filter" Width="100" Margin="2" Command="{Binding FilterChildrenCommand}" CommandParameter="{Binding ElementName=ChildFilterInput, Path=Text}"/>
</Grid>
</Grid>
</Grid>
</Window>
I've tried various implementations of adding a Filter event handler on the CollectionViewSource but have been unable to make them dynamic. It also seems like most examples/tutorials only deal directly with the parent item or static filters.
In a non-MVVM mindset, I was thinking to have an interaction trigger drive the selected item back into the ViewModel, and then create a filtered ICollectionView which the ChildItems ListView would bind to, but it seems like I can't be the only person trying this, and that there must be an easier MVVM binding friendly way.
The following example shows a simple solution to implement live filtering on a collection:
Person.cs
class Person
{
public Person(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
ViewModel.cs
class ViewModel
{
public ViewModel()
{
this.Persons = new ObservableCollection<Person>()
{
new Person("Derek", "Zoolander"),
new Person("Tony", "Montana"),
new Person("John", "Wick"),
new Person("The", "Dude"),
new Person("James", "Bond"),
new Person("Walter", "White")
};
}
private void FilterData(string filterPredicate)
{
// Execute live filter
CollectionViewSource.GetDefaultView(this.Persons).Filter =
item => (item as Person).FirstName.StartsWith(filterPredicate, StringComparison.OrdinalIgnoreCase);
}
private string searchPredicate;
public string SearchPredicate
{
get => this.searchFilter;
set
{
this.searchPredicate = value;
FilterData(value);
}
}
public ObservableCollection<Person> Persons { get; set; }
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding SearchPredicate, UpdateSourceTrigger=PropertyChanged"} />
<ListView ItemsSource="{Binding Persons}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Firstname" DisplayMemberBinding="{Binding FirstName}" />
<GridViewColumn Header="Lastname" DisplayMemberBinding="{Binding LastName}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Window>
Update
It seems like you are having problems to filter the child items. The following example is more specific to your scenario:
DataItem.cs
class DataItem
{
public DataItem(string Name)
{
this.Name = name;
}
public string Name { get; set; }
public ObservableCollection<DataItem> ChildItems { get; set; }
}
ViewModel.cs
class ViewModel
{
public ViewModel()
{
this.ParentItems = new ObservableCollection<DataItem>()
{
new DataItem("Ben Stiller") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("Zoolander"), new DataItem("Tropical Thunder") }},
new DataItem("Al Pacino") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("Scarface"), new DataItem("The Irishman") }},
new DataItem("Keanu Reeves") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("John Wick"), new DataItem("Matrix") }},
new DataItem("Bryan Cranston") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("Breaking Bad"), new DataItem("Malcolm in the Middle") }}
};
}
private void FilterData(string filterPredicate)
{
// Execute live filter
CollectionViewSource.GetDefaultView(this.SelectedParentItem.ChildItems).Filter =
item => (item as DataItem).Name.StartsWith(filterPredicate, StringComparison.OrdinalIgnoreCase);
}
private string searchPredicate;
public string SearchPredicate
{
get => this.searchFilter;
set
{
this.searchPredicate = value;
FilterData(value);
}
}
public ObservableCollection<DataItem> ParentItems { get; set; }
public DataItem SelectedParentItem { get; set; }
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<StackPanel>
<ListView ItemsSource="{Binding ParentItems}"
SelectedItem="{Binding SelectedParentItem}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBox Text="{Binding SearchPredicate, UpdateSourceTrigger=PropertyChanged}" />
<ListView ItemsSource="{Binding SelectedParentItem.ChildItems}" />
</StackPanel>
</Window>
Using the examples from #BionicCode- I added a SelectedParentItem INPC property to the ViewModel, performed the filtering on that via CollectionViewSource.Filter, and bound the ChildItems ListView to SelectedParentItem.ChildItems.
I did not bind the text box property changed to a backing field in the VM as per #BionicCode's example, as the "real" ChildItems might be in the mid 10,000's and I didn't want it to filter on each keystroke. So this answer implements the filter button and text box command, and the CanFilterChildren is doing a proper null check.
MainWindowViewModel.cs:
public class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<ParentItem> ParentItems { get; set; }
private ParentItem _selectedParentItem;
public ParentItem SelectedParentItem
{
get { return _selectedParentItem; }
set { SetProperty(ref _selectedParentItem, value); }
}
public MainWindowViewModel()
{
ParentItems = new ObservableCollection<ParentItem>();
LoadDummyParentItems();
}
private ICommand _filterChildrenCommand;
public ICommand FilterChildrenCommand => _filterChildrenCommand ?? (_filterChildrenCommand = new RelayCommand(param => FilterChildren((string)param), param => CanFilterChildren((string)param)));
private bool CanFilterChildren(string filter) => SelectedParentItem != null && filter.Length > 0;
private void FilterChildren(string filter)
{
CollectionViewSource.GetDefaultView(SelectedParentItem.ChildItems).Filter = item => (item as string).Contains(filter);
}
private void LoadDummyParentItems()
{
for (var i = 0; i < 20; i++)
{
ParentItems.Add(new ParentItem()
{
Name = $"Parent Item {i}",
CreatedOn = DateTime.Now.AddHours(i),
IsActive = i % 2 == 0 ? true : false,
Status = i % 2 == 0 ? ParentItemStatus.Status_Two : ParentItemStatus.Status_One,
ChildItems = new List<string>() { $"Child one_{i}", $"Child two_{i}", $"Child three_{i}", $"Child four_{i}" }
});
}
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainWindowViewModel();
this.DataContext = _viewModel;
}
}
MainWindow.xaml:
<Window x:Class="FilteringDemo.Views.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:FilteringDemo.Views"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".25*"/>
<ColumnDefinition Width=".75*"/>
</Grid.ColumnDefinitions>
<ListView x:Name="ItemList" Grid.Column="0" Margin="2" ItemsSource="{Binding ParentItems}" SelectedItem="{Binding SelectedParentItem, Mode=OneWayToSource}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ElementName=ItemList, Path=SelectedItem.Name}" Margin="2"/>
<TextBlock Grid.Column="1" Text="{Binding ElementName=ItemList, Path=SelectedItem.CreatedOn}" Margin="2"/>
<TextBlock Grid.Column="2" Text="{Binding ElementName=ItemList, Path=SelectedItem.IsActive}" Margin="2"/>
<TextBlock Grid.Column="3" Text="{Binding ElementName=ItemList, Path=SelectedItem.Status}" Margin="2"/>
</Grid>
<ListView Grid.Row="1" Margin="2" ItemsSource="{Binding SelectedParentItem.ChildItems}" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="ChildFilterInput" Grid.Column="0" Margin="2">
<TextBox.InputBindings>
<KeyBinding Command="{Binding FilterChildrenCommand}" CommandParameter="{Binding ElementName=ChildFilterInput, Path=Text}" Key="Return" />
</TextBox.InputBindings>
</TextBox>
<Button Grid.Column="1" Content="Filter" Width="100" Margin="2" Command="{Binding FilterChildrenCommand}" CommandParameter="{Binding ElementName=ChildFilterInput, Path=Text}"/>
</Grid>
</Grid>
</Grid>
</Window>
ViewModelBase.cs:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
RelayCommand.cs:
public class RelayCommand : ICommand
{
private Predicate<object> _canExecuteMethod;
private Action<object> _executeMethod;
public RelayCommand(Action<object> executeMethod, Predicate<object> canExecuteMethod = null)
{
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecuteMethod == null ? true : _canExecuteMethod(parameter);
}
public void Execute(object parameter)
{
_executeMethod(parameter);
}
}
ParentItem.cs:
public class ParentItem
{
public string Name { get; set; }
public List<string> ChildItems { get; set; }
public DateTime CreatedOn { get; set; }
public bool IsActive { get; set; }
public ParentItemStatus Status { get; set; }
}
public enum ParentItemStatus
{
Status_One,
Status_Two
}
App.xaml:
<Application x:Class="FilteringDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FilteringDemo"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Note: The MainWindow.xaml file was moved into a "Views" folder, so I'm including the App.xaml with the updated StartupUri in case anyone is trying to copy and paste.
I am practicing MVVM application and i am beginner, what i am trying to do is, I have 3 rows in a grid, the first row will contain 3 labels and 3 corresponding buttons. And the second row will contain a button to save the data entered in that textboxes in first row. The third row will contain a datagrid with the same number and type of textboxes (three).
Look can be seen here http://prntscr.com/9v2336
The user will enter the data in first row and then he will press save button in second row and then he must find the written information in the corresponding datagrid columns.
My try is here (Entire Code):
View:
<Window x:Class="WpfApplication4.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.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding TextName}" Height="20" Width="80" HorizontalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding RollNumber}" Height="20" Width="80"></TextBox>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Class}" Height="20" Width="80"></TextBox>
<Label Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">Name</Label>
<Label Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">RollNumber</Label>
<Label Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">Class</Label>
</Grid>
<Grid Grid.Row="1" >
<Button Width="80" Height="20" Command="{Binding saveStudentRecord}"> Save</Button>
</Grid>
<Grid Grid.Row="2">
<DataGrid ItemsSource="{Binding DGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding dgName}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Rollnumber" Binding="{Binding dgRollnumber}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Class" Binding="{Binding dgClass}" Width="150"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>
Model:
class Model
{
private string textName;
public string TextName
{
get { return textName; }
}
private string rollNumber;
public string RollNumber
{
get { return rollNumber; }
}
private string cclass;
public string Class
{
get { return cclass; }
}
}
ViewModel:
class ViewModel
{
public bool canExecute { get; set; }
private RelayCommand saveStudentRecord;
private ObservableCollection<Model> dGrid;
public ViewModel()
{
}
private void MyAction()
{
//What to do here to pass all that data to the datagrid corresponding columns
}
}
Where i have problem ?
I have designed the entire body but i am not able to find the logic that how would i assign the entered data in text boxes to corresponding data-grid columns on button click event and binding themusing only MVVM.
Should be simple as adding a new Model to your ObservableCollection<Model> DGrid:
class ViewModel
{
public bool canExecute { get; set; }
private RelayCommand saveStudentRecord;
private ObservableCollection<Model> dGrid;
public ViewModel()
{
dGrid = new ObservableCollection<Model>();
}
private void MyAction()
{
dGrid.Add(new Model(){
TextName = valueOfTextTextBox,
RollNumber = valueOfRollNumberTextBox,
Class = valueOfClassTextBox
});
}
}
Thing to remember here:
dGrid should be a public property, so you can use Databinding with it, e.g.:
public ObservableCollection<Model> dGrid {
get;
private set;
}
EDIT based on the question in commments:
You need to bind the text property of TextBoxes to a property on you ViewModel. As ModelClass holds that kind of info, I would do:
class ViewModel
{
public bool canExecute { get; set; }
private RelayCommand saveStudentRecord;
private ObservableCollection<Model> dGrid;
public ViewModel()
{
dGrid = new ObservableCollection<Model>();
}
public Model EditedModel {
get {
return _editedModel;
}
set {
_editedModel = value;
SignalPropertyChanged("EditedModel");
}
}
private void MyAction()
{
dGrid.Add(EditedModel);
EditedModel = new Model();
}
void SignalPropertyChanged(string propertyName){
if(propertyChanged !=null){
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Of course now your ViewModel and Model need to implement INotifyPropertyChanged interface to notify view of the changes
class Model : INotifyPropertyChanged
{
private string textName;
public string TextName
{
get { return textName; }
set {
textName = value;
SignalPropertyChanged("TextName");
}
}
private string rollNumber;
public string RollNumber
{
get { return rollNumber; }
set {
rollNumber= value;
SignalPropertyChanged("RollNumber");
}
}
private string cclass;
public string Class
{
get { return cclass; }
set {
cclass= value;
SignalPropertyChanged("Class");
}
}
public event PropertyChangedEventHandler propertyChanged;
void SignalPropertyChanged(string propertyName){
if(propertyChanged !=null){
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
EDIT2 - forgot to add XAML part :) You need to bind the textboxes to a new property
<Window x:Class="WpfApplication4.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.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding EditedModel.TextName}" Height="20" Width="80" HorizontalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding EditedModel.RollNumber}" Height="20" Width="80"></TextBox>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding EditedModel.Class}" Height="20" Width="80"></TextBox>
<Label Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">Name</Label>
<Label Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">RollNumber</Label>
<Label Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">Class</Label>
</Grid>
<Grid Grid.Row="1" >
<Button Width="80" Height="20" Command="{Binding saveStudentRecord}"> Save</Button>
</Grid>
<Grid Grid.Row="2">
<DataGrid ItemsSource="{Binding DGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding dgName}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Rollnumber" Binding="{Binding dgRollnumber}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Class" Binding="{Binding dgClass}" Width="150"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>
In XAML I have a list view:
<UserControl x:Class="HspSetup.View.UserInputData"
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:HspSetup.View"
xmlns:viewModel="clr-namespace:HspSetup.ViewModel"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:Behaviours="clr-namespace:HspSetup.Behaviours"
mc:Ignorable="d"
Height="Auto" Width="Auto">
<UserControl.Resources>
<viewModel:UserInputDataViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="0.1*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="0.5*" />
</Grid.ColumnDefinitions>
<TextBlock Background="Coral" Height="50"
VerticalAlignment="Center" TextAlignment="Center"
Grid.Row="0" Grid.ColumnSpan="3">
<TextBlock.Text>Some More Data</TextBlock.Text>
<TextBlock.FontFamily>Segoe UI</TextBlock.FontFamily>
<TextBlock.Foreground>White</TextBlock.Foreground>
<TextBlock.FontSize>30</TextBlock.FontSize>
</TextBlock>
<ListView Grid.Row="2" Width="Auto"
Behaviours:GridViewColumnResize.Enabled="True"
Height="Auto" Name="OrderNumberListView"
IsSynchronizedWithCurrentItem="True" Grid.Column="1"
DataContext="{Binding Source={StaticResource ViewModel}}"
ItemsSource="{Binding ConfigObjects}" >
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Behaviours:LostFocus.LostFocusCommand"
Value="{Binding Path=LostFocusCommand, Source={StaticResource ViewModel}}"/>
<Setter Property="Behaviours:LostFocus.CommandParem"
Value="{Binding}"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Order Name"
Behaviours:GridViewColumnResize.Width="*">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding OrderNumber, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource flatTextBox}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Type Name"
Behaviours:GridViewColumnResize.Width="*">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding TypeName, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource flatTextBox}">
</TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Behaviours:GridViewColumnResize.Width="*" >
<GridViewColumn.HeaderTemplate>
<DataTemplate>
<Button Content="Add"
Command="{Binding AddConfigObjectCommand, Mode=OneWay, Source={StaticResource ViewModel}}"/>
</DataTemplate>
</GridViewColumn.HeaderTemplate>
<GridViewColumnHeader HorizontalContentAlignment="Stretch"/>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Name="RemoveButton"
Content="Remove"
Command="{Binding RemoveConfigObjectCommand, Mode=OneWay, Source={StaticResource ViewModel}}" CommandParameter="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
The Corresponding UI looks like this:
On Click of an Add button I add an Empty row as follows :
I Enter text into the text boxes.
The change made to the listview by entering text in the textbox is not updating the observableCollection to which the textboxes are bound.
The class of the observable collection is as below :
class UserInputDataViewModel: BaseViewModel
{
private ObservableCollection<ConfigObject> _configObjects;
public ObservableCollection<ConfigObject> ConfigObjects
{
get { return _configObjects; }
set
{
_configObjects = value;
OnPropertyChanged("ConfigObjects");
}
}
private ICommand _addConfigObject;
public ICommand AddConfigObjectCommand
{
get
{
if (_addConfigObject == null)
{
_addConfigObject = new RelayCommand(
p => AddConfigObject(),
p => CanAddConfigObject);
}
return _addConfigObject;
}
}
private void AddConfigObject()
{
ConfigObjects.Add(new ConfigObject() { OrderNumber = "", TypeName = "" });
OnPropertyChanged("ConfigObjects");
}
private ICommand _removeConfigObject;
public ICommand RemoveConfigObjectCommand
{
get
{
if (_removeConfigObject == null)
{
_removeConfigObject = new RelayCommand(
p => RemoveConfigObject((ConfigObject)p),
p => CanRemoveConfigObject);
}
return _removeConfigObject;
}
}
private void RemoveConfigObject(ConfigObject configObject)
{
ConfigObjects.Remove(configObject);
OnPropertyChanged("ConfigObjects");
}
bool CanAddConfigObject
{
get { return true; }
}
bool CanRemoveConfigObject
{
get { return true; }
}
}
class BaseViewModel:INotifyPropertyChanged
{
/// <summary>
/// Name of the property that changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Send the notification to the property that change
/// </summary>
/// <param name="pPropertyName">name of the property</param>
protected virtual void OnPropertyChanged(string pPropertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this,
new PropertyChangedEventArgs(pPropertyName));
}
}
}
internal class ConfigObject : INotifyPropertyChanged
{
private string _orderNumber;
public string OrderNumber
{
get { return _orderNumber; }
set { _orderNumber = value; OnPropertyChanged("OrderNumber"); }
}
private string _typeName;
public string TypeName
{
get { return _typeName; }
set { _typeName = value; OnPropertyChanged("TypeName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string paramname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(paramname));
}
}
By editing the text in the textboxes you do not change the ObservableCollection but you change one of the objects inside the collection. The collection is not aware of changing the internal(!) state of one the objects in it. Those changes can be captured by the PropertyChanged event of your ConfigObject class.
I'm learning WPF and I'm trying to do something simple. I have two classes: Candy and MyColor. The code of these two classes look like this
public class Candy
{
public MyColor Color { get; set; }
public string Name { get; set; }
}
public class MyColor
{
public string Name { get; set; }
public uint Id { get; set; }
}
(I have attached an image below to make it clearer)
I have an area in the window in which I can create a MyColor by using a textbox that inserts the MyColor.Name, and a simple logic which increments the MyColor.Id. On the other side of the window, I have a button that creates new item in a ItemsControl which holds Candy. Within this ItemsControl there is an ComboBox which I can specify Candy.Color and a TextBox which I specify the Candy.Name. Finally, when I hit the button Generate List the code should output in the TextBox below a list in the format of
Candy.Color Candy.Name
I'm trying to figure out how to automatically populate the ComboBox filled with a list of Colors I have created so I can specify the Candy color, but I don't know how to bind my data source. Also, how would I generate the text?
Currently my code looks like this
namespace QuestionToAsk
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<MyColor> Colors;
ObservableCollection<Candy> Candies;
public MainWindow()
{
InitializeComponent();
Colors = new ObservableCollection<MyColor>();
Candies = new ObservableCollection<Candy>();
Colors.Add(new MyColor() { Name = "(Unspecified)", Id = 0 });
icColors.ItemsSource = Colors;
icCandies.ItemsSource = Candies;
}
private void btnColor(object sender, RoutedEventArgs e)
{
if (txtColor.Text != "")
{
uint last_id = Colors.Last<MyColor>().Id;
Colors.Add(new MyColor() { Name = txtColor.Text, Id = last_id+1 });
txtColor.Text = "";
}
}
private void btnNewCandy(object sender, RoutedEventArgs e)
{
Candies.Add(new Candy());
}
private void btnGetList(object sender, RoutedEventArgs e)
{
//How to create the list of <Color, Name>?
}
}
public class Candy
{
public MyColor Color { get; set; }
public string Name { get; set; }
}
public class MyColor
{
public string Name { get; set; }
public uint Id { get; set; }
}
}
And my XML file looks like this:
<Window x:Class="QuestionToAsk.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:QuestionToAsk"
Title="Color Candy Maker" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel Margin="3">
<Button Content="Add Color" Click="btnColor" DockPanel.Dock="Bottom"/>
<TextBox x:Name="txtColor" DockPanel.Dock="Bottom"/>
<ItemsControl x:Name="icColors" Grid.Column="0" Grid.Row="0" DockPanel.Dock="Top">
<ItemsControl.ItemTemplate>
<DataTemplate x:Name="tColorsTemplate">
<TextBlock Text="{Binding Name}" Name="Color" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<DockPanel Grid.Column="1" Grid.Row="0" Margin="3">
<Grid DockPanel.Dock="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Content="New Candy" Click="btnNewCandy" Grid.Column="0"/>
<Button Content="Generate List" Click="btnGetList" Grid.Column="1"/>
</Grid>
<ItemsControl Name="icCandies" DockPanel.Dock="Top">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ComboBox Name="cmbColors" Grid.Column="0">
<!-- How to bind this cmbColors to icColors? -->
</ComboBox>
<TextBox Text="{Binding Name}" Grid.Column="1" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<TextBox x:Name="txtColorCandy"/>
</DockPanel>
</Grid>
</Window>
EDIT
I have removed all my ideas and implemented it using your hard design :D.
The xaml code
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel Margin="3">
<Button Content="Add Color" Click="btnColor" DockPanel.Dock="Bottom"/>
<TextBox x:Name="txtColor" DockPanel.Dock="Bottom"/>
<ItemsControl x:Name="icColors" Grid.Column="0" Grid.Row="0" DockPanel.Dock="Top">
<ItemsControl.ItemTemplate>
<DataTemplate x:Name="tColorsTemplate">
<TextBlock Text="{Binding Name}" Name="Color" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<DockPanel Grid.Column="1" Grid.Row="0" Margin="3">
<Grid DockPanel.Dock="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Content="New Candy" Click="btnNewCandy" Grid.Column="0"/>
<Button Content="Generate List" Click="btnGetList" Grid.Column="1"/>
</Grid>
<ItemsControl Name="icCandies" DockPanel.Dock="Top">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ComboBox Name="cmbColors" Grid.Column="0"
ItemsSource="{Binding Colors, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedItem="{Binding Color}">
</ComboBox>
<TextBox Text="{Binding Name}" Grid.Column="1" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<TextBox x:Name="txtColorCandy" VerticalScrollBarVisibility="Auto"/>
</DockPanel>
</Grid>
Note the usage of RelativeSource since the values of ComboBox was in a property of the Window class while the selected value was to be saved in the Candy class. So the ItemsSource is bound to a property of Window class and the SelectedItem is bound to a property of the Candy class
Code Behind
public partial class MainWindow : Window
{
private ObservableCollection<MyColor> _colors;
public IEnumerable<MyColor> Colors
{
get { return _colors; }
}
private ObservableCollection<Candy> _candies;
public IEnumerable<Candy> Candies
{
get { return _candies; }
}
public MainWindow()
{
InitializeComponent();
_colors = new ObservableCollection<MyColor>();
_candies = new ObservableCollection<Candy>();
_colors.Add(new MyColor { Name = "(Unspecified)", Id = 0 });
icColors.ItemsSource = Colors;
icCandies.ItemsSource = Candies;
}
private void btnColor(object sender, RoutedEventArgs e)
{
if (txtColor.Text != "")
{
uint last_id = Colors.Last<MyColor>().Id;
_colors.Add(new MyColor() { Name = txtColor.Text, Id = last_id + 1 });
txtColor.Text = "";
}
}
private void btnNewCandy(object sender, RoutedEventArgs e)
{
_candies.Add(new Candy());
}
private void btnGetList(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach (var item in _candies)
{
if (item.Name == null || item.Color == null)
continue;
sb.AppendLine(item.Color.Name + " " + item.Name);
}
txtColorCandy.Text = sb.ToString();
}
}
It there are any confusion let me know and I will try to help