WPF: Bindings break when CollectionChanges - why? - c#

First, let me apologize for the length of this post - I know it's long, but I figured for this one, more detail is better than less.
What I'm trying to achieve now is a totals footer row for a datagrid. Since it needs to show up on the bottom row, the approach I'm taking is to just add some TextBlocks that line up with the columns in the datagrid.
My app has multiple datagrids inside an ItemsControl so I haven't found a nice way of just setting a binding. Using RelativeSource doesn't seem to be an option as there's no way (as far as I can tell) to point it at a descendent element, then search for a particular child. So instead I've written a bit of hackery to do what I want in code behind.
Ok, so now the problem.. Everything looks fine when the app starts, but as soon as any of the items in the grid change, the width binding seems to break completely. I wrote a small test app to show what I mean. Here's some screenshots:
Now if I click the button to change an element, the width binding of the footer textblocks breaks:
I'm completely stumped as to the cause of this behavior. I'm pretty new to WPF and just muddling my way through building my first app. So if this is a dumb way of doing things, please let me know. Here's my code.
MainWindow.xaml:
<Window x:Class="WpfTestApp.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:WpfTestApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Vertical">
<ItemsControl x:Name="BarsItemsControl" ItemsSource="{Binding Bars}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Description}" />
<DataGrid x:Name="FooGrid"
ItemsSource="{Binding Foos}"
IsSynchronizedWithCurrentItem="False"
AutoGenerateColumns="False"
SelectionUnit="Cell"
SelectionMode="Extended"
CanUserReorderColumns="False"
CanUserAddRows="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="Col 1" Width="*" Binding="{Binding Value1}" />
<DataGridTextColumn Header="Col 2" Width="*" Binding="{Binding Value2}" />
<DataGridTextColumn Header="Col 3" Width="*" Binding="{Binding Value3}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel x:Name="TotalsRow" Orientation="Horizontal">
<TextBlock Text="{Binding Totals[0]}" />
<TextBlock Text="{Binding Totals[1]}" />
<TextBlock Text="{Binding Totals[2]}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Change something" Click="Button_Click" />
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace WpfTestApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public IList<Bar> Bars { get; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Bars = new ObservableItemsCollection<Bar>();
var foos = new ObservableItemsCollection<Foo>();
for (int i = 0; i < 5; i++)
{
foos.Add(new Foo()
{
Value1 = 14.23,
Value2 = 53.23,
Value3 = 35.23
});
}
var foos2 = new ObservableItemsCollection<Foo>();
for (int i = 0; i < 5; i++)
{
foos2.Add(new Foo()
{
Value1 = 14.23,
Value2 = 53.23,
Value3 = 35.23
});
}
this.Bars.Add(new Bar(foos)
{
Description = "Bar 1",
});
this.Bars.Add(new Bar(foos2)
{
Description = "Bar 2",
});
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// Bind widths of the TotalsRow textblocks (footers) to the width of the
// datagrid column they're associated with
var elements = new List<FrameworkElement>();
this.GetChildElementsByName(this.BarsItemsControl, "FooGrid", ref elements);
foreach (var element in elements)
{
var dataGrid = element as DataGrid;
if (dataGrid != null)
{
var totalsRowList = new List<FrameworkElement>();
this.GetChildElementsByName(VisualTreeHelper.GetParent(dataGrid), "TotalsRow", ref totalsRowList);
if (totalsRowList.Count > 0)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(totalsRowList[0]); i++)
{
var textBlock = VisualTreeHelper.GetChild(totalsRowList[0], i) as TextBlock;
Binding widthBinding = new Binding();
widthBinding.Source = dataGrid.Columns[i];
widthBinding.Path = new PropertyPath("ActualWidth");
widthBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(textBlock, TextBlock.WidthProperty, widthBinding);
}
}
}
}
}
/// <summary>
/// Populate a list of elements in the visual tree with the given name under the given parent
/// </summary>
public void GetChildElementsByName(DependencyObject parent, string name, ref List<FrameworkElement> elements)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
var element = child as FrameworkElement;
if (element != null && element.Name == name)
{
elements.Add(element);
}
GetChildElementsByName(child, name, ref elements);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Bars[0].Foos[3].Value1 = 10;
}
}
}
Foo.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfTestApp
{
public class Foo : INotifyPropertyChanged
{
private double value1;
public double Value1 {
get { return value1; }
set { value1 = value; OnPropertyChanged(); }
}
private double value2;
public double Value2
{
get { return value2; }
set { value2 = value; OnPropertyChanged(); }
}
private double value3;
public double Value3
{
get { return value3; }
set { value3 = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
Bar.cs
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace WpfTestApp
{
public class Bar : INotifyPropertyChanged
{
public Bar(ObservableItemsCollection<Foo> foos)
{
this.Foos = foos;
this.Totals = new double[3] { 14, 14, 14};
this.Foos.CollectionChanged += Foos_CollectionChanged;
}
private void Foos_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//var fooList = this.Categories.Cast<CategoryViewModel>();
this.Totals[0] = this.Foos.Sum(f => f.Value1);
this.Totals[1] = this.Foos.Sum(f => f.Value2);
this.Totals[2] = this.Foos.Sum(f => f.Value3);
OnPropertyChanged(nameof(Totals));
}
public string Description { get; set; }
public ObservableItemsCollection<Foo> Foos { get; }
public double[] Totals { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
ObservableItemsCollection.cs
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace WpfTestApp
{
public class ObservableItemsCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
private void Handle(object sender, PropertyChangedEventArgs args)
{
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, null));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (object t in e.NewItems)
{
((T)t).PropertyChanged += Handle;
}
}
if (e.OldItems != null)
{
foreach (object t in e.OldItems)
{
((T)t).PropertyChanged -= Handle;
}
}
base.OnCollectionChanged(e);
}
}
}

Just bind to the ActualWidth of the columns:
<Window x:Class="WpfTestApp.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:WpfTestApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Vertical">
<ItemsControl x:Name="BarsItemsControl" ItemsSource="{Binding Bars}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Description}" />
<DataGrid x:Name="FooGrid"
ItemsSource="{Binding Foos}"
IsSynchronizedWithCurrentItem="False"
AutoGenerateColumns="False"
SelectionUnit="Cell"
SelectionMode="Extended"
CanUserReorderColumns="False"
CanUserAddRows="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn x:Name="col1" Header="Col 1" Width="*" Binding="{Binding Value1}" />
<DataGridTextColumn x:Name="col2" Header="Col 2" Width="*" Binding="{Binding Value2}" />
<DataGridTextColumn x:Name="col3" Header="Col 3" Width="*" Binding="{Binding Value3}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel x:Name="TotalsRow" Orientation="Horizontal">
<TextBlock Width="{Binding ElementName=col1, Path=ActualWidth}" Text="{Binding Totals[0]}" />
<TextBlock Width="{Binding ElementName=col2, Path=ActualWidth}" Text="{Binding Totals[1]}" />
<TextBlock Width="{Binding ElementName=col3, Path=ActualWidth}" Text="{Binding Totals[2]}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Change something" Click="Button_Click" />
</StackPanel>

Change your last stackpanel to grid in your xaml source:
<Grid x:Name="TotalsRow">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Totals[0]}" Grid.Column="0"/>
<TextBlock Text="{Binding Totals[1]}" Grid.Column="1"/>
<TextBlock Text="{Binding Totals[2]}" Grid.Column="2"/>
</Grid>
Result:

Related

Item Source in Data Grid Data Template not working

I am trying to show MultiSelectComboBox in Datagrid cell but even though combo box works alone, it shows empty if I put it inside the data grid.
I think it is related to <UserControl.Resources> vs <DataGrid.Resources> but I couldn't find a fix.
You can find the solution from here.
My Model:
public class Blocks : BaseViewModel
{
private bool _isSelected;
private int _id;
private string _name;
public Blocks(string name)
{
_name = name;
}
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected == value) return;
_isSelected = value;
OnPropertyChanged();
}
}
public int ID
{
get => _id;
set
{
if (_id == value) return;
_id = value;
OnPropertyChanged();
// multicombox default filtreleme için name kullanıyor
_name = value.ToString();
}
}
public string Name
{
get => _name;
set
{
if (_name != null && string.Compare(_name, value, StringComparison.InvariantCulture) == 0)
{
return;
}
_name = value;
OnPropertyChanged();
}
}
public override string ToString()
{
return ID.ToString();
}
}
My ViewModel:
public class ListItemsViewModel : BaseViewModel
{
private ObservableCollection _routes;
public ObservableCollection<int> Routes
{
get => _routes ?? (_routes = new ObservableCollection<int>());
set
{
_routes = value;
OnPropertyChanged();
}
}
// multi combo box
private ICommand _selectedStatusItemsChangedCommand;
private ObservableCollection<Blocks> _includedBlocks;
private ObservableCollection<Blocks> _selectedBlocks;
public ObservableCollection<Blocks> IncludedBlocks
{
get => _includedBlocks ?? (_includedBlocks = new ObservableCollection<Blocks>());
set
{
_includedBlocks = value;
OnPropertyChanged();
}
}
public ObservableCollection<Blocks> SelectedBlocks
{
get => _selectedBlocks ?? (_selectedBlocks = new ObservableCollection<Blocks>());
set
{
_selectedBlocks = value;
OnPropertyChanged();
}
}
public ICommand SelectedItemsChangedCommand
=> _selectedStatusItemsChangedCommand ?? (_selectedStatusItemsChangedCommand = new CommandHandler(SelectedItemsChanged));
public ListItemsViewModel(ObservableCollection<Blocks> list, ObservableCollection<int> ints)
{
_includedBlocks = list;
_routes = ints;
}
// Multi select combo box
public int SelectedStatusItemsCount { get; set; }
private void UpdateSelectedStatusItemsCount(int count)
{
SelectedStatusItemsCount = count;
OnPropertyChanged();
}
private void SelectedItemsChanged(object parameter)
{
if (parameter is SelectedItemsChangedEventArgs args)
{
foreach (var virtualRailBlock in _includedBlocks)
{
var selectedItemIndex = args.Selected.Cast<Blocks>().ToList().IndexOf(virtualRailBlock);
virtualRailBlock.IsSelected = selectedItemIndex > -1;
}
UpdateSelectedStatusItemsCount(args.Selected.Count);
}
}
}
My View:
<UserControl x:Class="MultiSelectControlCodes.View.ListItemsView"
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:sdl="http://schemas.sdl.com/xaml"
xmlns:viewModel="clr-namespace:MultiSelectControlCodes.ViewModel"
xmlns:model="clr-namespace:MultiSelectControlCodes.Model"
mc:Ignorable="d" d:DesignHeight="200" d:DesignWidth="400"
d:DataContext="{d:DesignInstance viewModel:ListItemsViewModel}">
<Grid>
<DataGrid
ItemsSource="{Binding Routes}"
SelectionUnit="FullRow"
SelectionMode="Extended"
AutoGenerateColumns="False"
IsReadOnly="False"
CanUserAddRows="True"
CanUserDeleteRows="True">
<DataGrid.Resources>
<DataTemplate x:Key="MultiSelectComboBox.Dropdown.ListBox.ItemTemplate" DataType="Block">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding Path=Name}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="MultiSelectComboBox.SelectedItems.ItemTemplate" DataType="Block">
<StackPanel Orientation="Horizontal" Margin="0,-4">
<TextBlock VerticalAlignment="Center" Text="{Binding Path=Name}" Margin="2,0" />
</StackPanel>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns >
<DataGridTemplateColumn Header="Routes" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<sdl:MultiSelectComboBox
Margin="2"
VerticalAlignment="Top"
Height="50"
IsEditable="true"
SelectionMode="Multiple"
SelectedItems="{Binding SelectedBlocks}"
ItemsSource="{Binding IncludedBlocks}"
SelectedItemTemplate="{StaticResource MultiSelectComboBox.SelectedItems.ItemTemplate}"
DropdownItemTemplate="{StaticResource MultiSelectComboBox.Dropdown.ListBox.ItemTemplate}"
sdl:SelectedItemsChangedBehaviour.SelectedItemsChanged="{Binding SelectedItemsChangedCommand}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
Resulting window:
Working View:
<UserControl x:Class="MultiSelectControlCodes.View.ListItemsView"
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:sdl="http://schemas.sdl.com/xaml"
xmlns:viewModel="clr-namespace:MultiSelectControlCodes.ViewModel"
xmlns:model="clr-namespace:MultiSelectControlCodes.Model"
mc:Ignorable="d" d:DesignHeight="200" d:DesignWidth="400"
d:DataContext="{d:DesignInstance viewModel:ListItemsViewModel}">
<UserControl.Resources>
<DataTemplate x:Key="MultiSelectComboBox.Dropdown.ListBox.ItemTemplate" DataType="Block">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding Path=Name}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="MultiSelectComboBox.SelectedItems.ItemTemplate" DataType="Block">
<StackPanel Orientation="Horizontal" Margin="0,-4">
<TextBlock VerticalAlignment="Center" Text="{Binding Path=Name}" Margin="2,0" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<sdl:MultiSelectComboBox
Margin="2"
VerticalAlignment="Top"
Height="50"
IsEditable="true"
SelectionMode="Multiple"
SelectedItems="{Binding SelectedBlocks}"
ItemsSource="{Binding IncludedBlocks}"
SelectedItemTemplate="{StaticResource MultiSelectComboBox.SelectedItems.ItemTemplate}"
DropdownItemTemplate="{StaticResource MultiSelectComboBox.Dropdown.ListBox.ItemTemplate}"
sdl:SelectedItemsChangedBehaviour.SelectedItemsChanged="{Binding SelectedItemsChangedCommand}"/>
</Grid>
</UserControl>
Working Result:
Bind to the properties of the view model using a RelativeSource:
<sdl:MultiSelectComboBox
Margin="2"
VerticalAlignment="Top"
Height="50"
IsEditable="true"
SelectionMode="Multiple"
SelectedItems="{Binding DataContext.SelectedBlocks, RelativeSource={RelativeSource AncestorType=DataGrid}}"
ItemsSource="{Binding DataContext.IncludedBlocks, RelativeSource={RelativeSource AncestorType=DataGrid}}"
SelectedItemTemplate="{StaticResource MultiSelectComboBox.SelectedItems.ItemTemplate}"
DropdownItemTemplate="{StaticResource MultiSelectComboBox.Dropdown.ListBox.ItemTemplate}"
sdl:SelectedItemsChangedBehaviour.SelectedItemsChanged="{Binding SelectedItemsChangedCommand}"/>
The default DataContext the root element in a CellTemplate is the current item in the ItemsSource, i.e. an int in Routes in this case, and this one doesn't have any IncludedBlocks or SelectedBlocks property to bind to.

radio buttons inside datatemplate wpf c#

I'm trying to access radio buttons inside datatemplate which is in a datagrid. I came across a lot of answers but none work for me.
I have 3 different radio buttions in 3 different columns. i just cant figure out how to access those radio buttons and its killing me, i'v been struggling with it for days PLEASE HELP.
<DataGridTemplateColumn Header="Yes" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Opacity="0.8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<RadioButton x:Name="rbYes" GroupName="{Binding QuestionID}" Content="Yes" Grid.Column="0" />
</Grid>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="No" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Opacity="0.8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<RadioButton x:Name="rbNo" GroupName="{Binding QuestionID}" Content="No" Grid.Column="0" />
</Grid>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="N/A" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Opacity="0.8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<RadioButton x:Name="rbNA" GroupName="{Binding QuestionID}" Content="N/A" Grid.Column="0" />
</Grid>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And here is my code behind. I have changed it so many times.
RadioButton rbYes = (RadioButton)FindName("rbYes");
RadioButton rbNo = (RadioButton)FindName("rbNo");
RadioButton rbNA = (RadioButton)FindName("rbNA");
if (rbYes.IsChecked == true)
{
rbNo.IsChecked = false;
rbNA.IsChecked = false;
int questionID = Convert.ToInt32(drv["QuestionID"]);
questionAnswer = "Yes".ToString();
if (drv["Notes"].ToString() == "")
{
string notes = null;
}
else
{
string notes = drv["Notes"].ToString();
}
Assuming that you are familiar with data binding (as you have already bound the QuestionID) my suggestion is as follows;
Have 3 boolean properties say IsYes, IsNo, IsNA in the object through which the data is populated in the datagrid. Make the collection that is bound to the ItemSource of your datagrid as an ObservableCollection.
Bind the IsChecked of the radio buttons to these three properties respectively.
By doing the above steps, you don't have to worry about going deep into the cells of your datagrid to find the controls and to know whether they are set or not.
[UPDATE] Sorry for the delay. I was in a meeting. The Code is as follows.
Please note that this is a very rough mock up of how to achieve the concept which I mentioned above.
MainWindow.xaml
<Window x:Class="testWPFApplication.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:testWPFApplication"
mc:Ignorable="d" DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="LayoutRoot" >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0" SelectionMode="Single"
ItemsSource="{Binding Questions}" SelectedItem="{Binding SelectedQuestion,Mode=TwoWay}" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserDeleteRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Question Id" Binding="{Binding QuestionId}" IsReadOnly="True" />
<DataGridTextColumn Header="Question" Binding="{Binding QuestionDescription}" Width="3*" IsReadOnly="True" />
<DataGridTemplateColumn Header="Yes">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding QuestionId}" IsChecked="{Binding IsYes, Mode=TwoWay,UpdateSourceTrigger=LostFocus,NotifyOnTargetUpdated=True}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="No">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding QuestionId}" IsChecked="{Binding IsNo, Mode=TwoWay,UpdateSourceTrigger=LostFocus,NotifyOnTargetUpdated=True}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="N/A">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding QuestionId}" IsChecked="{Binding IsNA, Mode=TwoWay,UpdateSourceTrigger=LostFocus,NotifyOnTargetUpdated=True}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Grid.Row="1" Width="100" x:Name="btnCheckAnswers" HorizontalAlignment="Right" Margin="5" Click="btnCheckAnswers_Click" Content="Check Answers" />
</Grid>
</Window>
MainWindow.xaml.cs (code behind. can be moved a different ViewModel class)
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace testWPFApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Question> _questions;
private Question _selectedQuestion;
public ObservableCollection<Question> Questions
{
get { return _questions; }
set
{
_questions = value;
OnPropertyChanged("Questions");
}
}
public Question SelectedQuestion
{
get { return _selectedQuestion; }
set
{
_selectedQuestion = value;
OnPropertyChanged("SelectedQuestion");
OnSelectedQuestionChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string strPropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(strPropertyName));
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Init();
}
private void Init()
{
CreateMockData();
}
private void CreateMockData()
{
if (Questions == null)
{
Questions = new ObservableCollection<Question>();
}
else
{
Questions.Clear();
}
for (int i = 1; i <= 5; i++)
{
Question q = new Question
{
QuestionId = i,
QuestionDescription = "Sample Question " + i.ToString(),
IsYes = false,
IsNA = false,
IsNo = false
};
Questions.Add(q);
}
}
private void OnSelectedQuestionChanged()
{
if(SelectedQuestion != null)
{
}
}
private void btnCheckAnswers_Click(object sender, RoutedEventArgs e)
{
StringBuilder strQuestionAnswer = new StringBuilder();
foreach (Question item in Questions)
{
strQuestionAnswer.AppendLine(item.QuestionDescription + ":");
strQuestionAnswer.AppendLine("IsYes:" + item.IsYes);
strQuestionAnswer.AppendLine("IsNo:" + item.IsNo);
strQuestionAnswer.AppendLine("IsNA:" + item.IsNA);
}
MessageBox.Show(strQuestionAnswer.ToString());
}
}
public class Question
{
public int QuestionId { get; set; }
public string QuestionDescription { get; set; }
public bool IsYes { get; set; }
public bool IsNo { get; set; }
public bool IsNA { get; set; }
}
}
Window Load
Few values set
Result when the button is clicked
Hope this example helps you to address the issue that you have.
Thank you for answering so quickly. But thats not the problem, the problem is, I'm try to access the radiobuttons im using but i can find the buttons with the FindName function. Null is returned for rbYes, rbNo and rbNA. I don't know if my explanation is good enough.

WPF Datagrid comboboxcolumn with checkboxes

I have a datagrid in WPF bound to an ObservableCollection wich I read/save in a XML File. I'd now like to add a comboboxcolumn with checkboxes or something similar.
There should be a dropdown menu for selecting one or more weekdays.
Does anyone can help me out?
Thanks in advance!
Edit:
After implementing christoph's custom control(DropDownDayPicker), I could bind my data to it, but not in the other way(get the updated value(s) if it changes)
So here's what I tried:
My Object:
Entry.cs
public class Entry : INotifyPropertyChanged
{
string Id;
string ExecuteOn;
}
MyWindow.xaml.cs:
public ObservableCollection<Entry> entryList;
doc.Load("C:\\test\\list.xml");
XmlElement root = doc.DocumentElement;
XmlNodeList nodes = root.SelectNodes("Entry");
foreach(XmlNode node in nodes)
{
XmlNodeList subnodes = node.SelectNodes("ExecuteOn");
ObservableCollection<Weekday> days = new ObservableCollection<Weekday>();
foreach(XmlNode subnode in node["ExecuteOn"].ChildNodes)
days.Add( (Weekday)Enum.Parse(typeof(Weekday),subnode.InnerText));
_entryList.Add(new Entry(
node["Id"].InnerText,
node["Description"].InnerText,
node["Path"].InnerText,
Convert.ToInt32(node["KindOfTask"].InnerText),
days
));
}
MyWindow.xaml
<DataGrid x:Name="EntryView" ItemsControl.ItemsSource="{Binding EntryList}" DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
AutoGenerateColumns="false" Margin="0,34,0,37" CanUserAddRows="false" Height="Auto">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Days" x:Name="cellExecuteOn" Width="*" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:DropDownDayPicker SelectedWeekdays="{Binding ExecuteOn}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Here's what the xml looks like:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfEntry>
<Entry>
<Id>efbae4da-f833-4d07-a8af-9ec3421b4886</Id>
<ExecuteOn>
<Weekday>Montag</Weekday>
<Weekday>Dienstag</Weekday>
</ExecuteOn>
</Entry>
<Entry>
<Id>1cb13340-40dd-48c1-ada5-bbb6f79c0d06</Id>
<ExecuteOn>
<Weekday>Montag</Weekday>
</ExecuteOn>
</Entry>
</ArrayOfEntry>
You can use DataGridTemplateColumn. Try something like this:
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="Monday"/>
<CheckBox Content="Friday"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Hope that suits your needs.
Update:
Ok, now the professional approach. I made a custom control for what you want. (Please don´t rate the look, you can customize it). Here comes the code...
Weekday.cs
public enum Weekday
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
DropDownDayPicker.cs
public class DropDownDayPicker : Control
{
private List<CheckBox> checkboxes = new List<CheckBox>();
static DropDownDayPicker()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DropDownDayPicker), new FrameworkPropertyMetadata(typeof(DropDownDayPicker)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
StackPanel weekdayBoxes = this.GetTemplateChild("PART_weekdayHost") as StackPanel;
foreach(CheckBox box in weekdayBoxes.Children)
{
box.Checked += Box_CheckedChanged;
box.Unchecked += Box_CheckedChanged;
this.checkboxes.Add(box);
}
Button openPopup = this.GetTemplateChild("PART_openPopupButton") as Button;
openPopup.Click += OpenPopup_Click;
this.UpdateCheckboxes();
}
private void OpenPopup_Click(object sender, RoutedEventArgs e)
{
Popup popup = this.GetTemplateChild("PART_popup") as Popup;
popup.IsOpen = !popup.IsOpen;
}
private void Box_CheckedChanged(object sender, RoutedEventArgs e)
{
this.UpdateSelectedWeekdays();
}
public ObservableCollection<Weekday> SelectedWeekdays
{
get { return (ObservableCollection<Weekday>)GetValue(SelectedWeekdaysProperty); }
set { SetValue(SelectedWeekdaysProperty, value); }
}
public static readonly DependencyProperty SelectedWeekdaysProperty =
DependencyProperty.Register("SelectedWeekdays", typeof(ObservableCollection<Weekday>), typeof(DropDownDayPicker), new PropertyMetadata(new ObservableCollection<Weekday>(), SelectedWeekdaysPropertyChanged));
private static void SelectedWeekdaysPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
DropDownDayPicker picker = sender as DropDownDayPicker;
ObservableCollection<Weekday> oldValue = args.OldValue as ObservableCollection<Weekday>;
ObservableCollection<Weekday> newValue = args.NewValue as ObservableCollection<Weekday>;
if (picker != null)
{
if (oldValue != null)
{
oldValue.CollectionChanged -= picker.SelectedWeekdaysChanged;
}
if (newValue != null)
{
newValue.CollectionChanged += picker.SelectedWeekdaysChanged;
}
picker.UpdateCheckboxes();
}
}
private void SelectedWeekdaysChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
this.UpdateCheckboxes();
}
private bool updating = false;
private void UpdateCheckboxes()
{
if (!this.updating)
{
this.updating = true;
if (this.SelectedWeekdays != null)
{
foreach (CheckBox box in this.checkboxes)
{
box.IsChecked = this.SelectedWeekdays.Contains((Weekday)box.Tag);
}
}
this.UpdateSummary();
this.updating = false;
}
}
private void UpdateSelectedWeekdays()
{
if (!this.updating)
{
this.updating = true;
var selectedWeekdays = this.checkboxes.Where(x => x.IsChecked.HasValue && x.IsChecked.Value).Select(x => x.Tag).Cast<Weekday>();
this.SelectedWeekdays = new ObservableCollection<Weekday>(selectedWeekdays);
this.UpdateSummary();
this.updating = false;
}
}
private void UpdateSummary()
{
TextBlock summary = this.GetTemplateChild("PART_summary") as TextBlock;
if (this.SelectedWeekdays != null)
{
if (this.SelectedWeekdays.Count == 0)
{
summary.Text = "none";
}
else if (this.SelectedWeekdays.Count == 1)
{
summary.Text = this.SelectedWeekdays[0].ToString();
}
else if (this.SelectedWeekdays.Count > 1)
{
summary.Text = string.Format("{0} days",this.SelectedWeekdays.Count);
}
}
else
{
summary.Text = "none";
}
}
}
in Generic.xaml
<Style TargetType="{x:Type local:DropDownDayPicker}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Button x:Name="PART_openPopupButton">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Margin="3" x:Name="PART_summary"/>
<TextBlock FontFamily="Segoe UI Symbol" Text="" Grid.Column="1" FontWeight="Bold"/>
</Grid>
</Button>
<Popup PlacementTarget="{Binding ElementName=PART_openPopupButton}" IsOpen="False" x:Name="PART_popup" StaysOpen="False">
<StackPanel x:Name="PART_weekdayHost" Background="White">
<CheckBox Content="Monday">
<CheckBox.Tag>
<local:Weekday>Monday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Thusday">
<CheckBox.Tag>
<local:Weekday>Tuesday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Wednesday">
<CheckBox.Tag>
<local:Weekday>Wednesday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Thursday">
<CheckBox.Tag>
<local:Weekday>Thursday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Friday">
<CheckBox.Tag>
<local:Weekday>Friday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Saturday">
<CheckBox.Tag>
<local:Weekday>Saturday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Sunday">
<CheckBox.Tag>
<local:Weekday>Sunday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
</StackPanel>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Width" Value="150"/>
</Style>
and the datagrid
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:DropDownDayPicker SelectedWeekdays="{Binding whatEver}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I know this is pretty much effort but that is the way I would do it in a real world scenario.
Update 2:
I tried making a sample for you and noticed there is a small mistake I made. Please see DropDownDayPicker.cs and update method UpdateSelectedWeekdays like this:
private void UpdateSelectedWeekdays()
{
if (!this.updating)
{
this.updating = true;
var selectedWeekdays = this.checkboxes.Where(x => x.IsChecked.HasValue && x.IsChecked.Value).Select(x => x.Tag).Cast<Weekday>();
SetCurrentValue(DropDownDayPicker.SelectedWeekdaysProperty, new ObservableCollection<Weekday>(selectedWeekdays));
BindingExpression binding = this.GetBindingExpression(DropDownDayPicker.SelectedWeekdaysProperty);
if (binding != null)
{
binding.UpdateSource();
}
this.UpdateSummary();
this.updating = false;
}
}
So with that corrected lets come to the sample code.
Entry.cs
public class Entry : INotifyPropertyChanged
{
private string title;
public string Title
{
get { return title; }
set { title = value; this.OnPropertyChanged("Title"); }
}
private ObservableCollection<Weekday> days = new ObservableCollection<Weekday>();
public ObservableCollection<Weekday> Days
{
get { return days; }
set { days = value; this.OnPropertyChanged("Days"); }
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Viewmodel.cs
public class Viewmodel
{
private ObservableCollection<Entry> collection = new ObservableCollection<Entry>()
{
new Entry() { Title = "Entry 1" },
new Entry() { Title = "Entry 2" },
new Entry() { Title = "Entry 3" }
};
public ObservableCollection<Entry> Collection
{
get { return collection; }
set { collection = value; }
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Viewmodel/>
</Window.DataContext>
<StackPanel>
<Button Content="Click" Click="Button_Click_1"/>
<DataGrid ItemsSource="{Binding Collection}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Title}" Header="Title"/>
<DataGridTemplateColumn Header="Days">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:DropDownDayPicker SelectedWeekdays="{Binding Days, Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TextBlock Text="{Binding Collection[0].Days.Count}"/>
</StackPanel>
</Window>
Now when changing the days of the first row you see the count of selected days beneath the datagrid. Notice that the default binding mode of the selected days is OneWay. You have to assign Mode=TwoWay in order to make it working.
Try it out and give me some feedback.
Similar to what christoph had you can implement a combobox with something like this
<DataGrid ItemsSource="{Binding SomeCollection}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Id">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Week Days">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Days}"/>
<CheckBox/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Where Days is an enum property in a ObservableCollection of that class
public class SomeData
{
public int Id { get; set; }
public Days Days { get; set; }
}
public enum Days
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<SomeData> SomeCollection { get; set; }
}
Here is a good resource to learn about wpf datagrids
Datagrid tutorial

Data-binding to KVP of Object,Object not allowing properties to be used or updated in real time?

I have Operation class and Result class that I use to build a list, I use xaml to bind to the properties and a converter to return some content based on certain properties, Most of this works....
To make it easy I will post the xaml then note what works and what I need help with.
<UserControl x:Class="OperationListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:prism="http://www.codeplex.com/prism"
xmlns:inf="clr-namespace:***;assembly=***"
xmlns:c="clr-namespace:****.***"
d:DesignHeight="300" d:DesignWidth="300">
<Control.Resources>
<c:LanguageTextConverter x:Key="langConverter" />
<c:ResultViewConverter x:Key="statusConverter" />
<c:OpDetailViewConverter x:Key="opConverter" />
</Control.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<telerik:RadGridView Name="OperationGrid"
Grid.Column="0"
RowHeight="75"
SelectionMode="Single"
SelectedItem="{Binding SelectedOperation, Mode=TwoWay}"
ItemsSource="{Binding Operations}"
IsReadOnly="True"
AutoGenerateColumns="False"
RowDetailsVisibilityMode="VisibleWhenSelected"
ShowGroupPanel="False"
RowIndicatorVisibility="Collapsed" >
<telerik:RadGridView.RowDetailsTemplate>
<DataTemplate>
<ContentControl Grid.Column="1" Template="{Binding Converter={StaticResource opConverter}}" />
</DataTemplate>
</telerik:RadGridView.RowDetailsTemplate>
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn Header="Name"
DataMemberBinding="{Binding Key.operationName, Converter={StaticResource langConverter}}"
Width="2*"
IsGroupable="False" />
<telerik:GridViewDataColumn Header="Result"
Width="1*"
MaxWidth="75">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<Grid>
<ContentControl Template="{Binding Value, Converter={StaticResource statusConverter}}"/>
</Grid>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
</telerik:GridViewDataColumn>
</telerik:RadGridView.Columns>
</telerik:RadGridView>
</Grid>
everything up to the final binding works, and even the final binding works to a point...
<ContentControl Template="{Binding Value, Converter={StaticResource statusConverter}}"/>
this works, when binding to Value the converter fires on load and loads the appropriate xaml.
However no additional changes update this value...
In the code behind at some point the Value.status is updated , the setter on the status property fires an propertyChanged. but nothing is caught on the front end.
public OverallStatus status
{
get { return this.Status; }
set { this.Status = value; onPropertyChanged(this, "status"); }
}
What is the property syntax to bind to a Value.Property (currently doesn't work at all) AND have it recognize the propertyChanged setter 3 levels down from the view.
I have created a sample showing nested properties. When I change a property value at button click, it is reflected in the grid. More can be read here :
Property path syntax
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="524.627" Width="862.313">
<StackPanel>
<DataGrid x:Name="Dgrd" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Value.Status.Name}" ClipboardContentBinding="{x:Null}" Header="Status"/>
</DataGrid.Columns>
</DataGrid>
<Button Content="Button" HorizontalAlignment="Left" Margin="29,38,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</StackPanel>
</Window>
using System;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Dgrd.ItemsSource = DataStore.Operations;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DataStore.Operations[0].Value.Status.Name = "N/A"; // change first item
}
}
public class DataStore
{
//static List<Operation> _operations;
public static List<Operation> Operations;
static DataStore()
{
Operations =
new List<Operation>() {
new Operation(){ Value = new RecordInner(){ Status = new OverallStatus (){ Name="Pending"}}},
new Operation(){ Value = new RecordInner(){ Status = new OverallStatus (){ Name="Started"}}},
new Operation(){ Value = new RecordInner(){ Status = new OverallStatus (){ Name="Completed"}}}
};
}
}
public class Operation
{
RecordInner _value;
public RecordInner Value
{
get { return _value; }
set { _value = value; }
}
}
public class RecordInner
{
OverallStatus _status;
public OverallStatus Status
{
get { return _status; }
set { _status = value; }
}
}
public class OverallStatus:INotifyPropertyChanged
{
string _name;
public string Name { get { return _name; } set { _name = value; OnPropertyChanged("name"); } }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}

Grouping in WPF

I am trying to modify an existing code which allows to add articles by rows and remove them wherever needed, the problem is that I am trying to find a way to group articles of the same category. So when the user adds a new article of DVD category, it will be directly added to that category (I tried to take some ideas from here, but with no success: http://msdn.microsoft.com/en-us/library/ff407126.aspx
This is the code behind data:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfDataGridWithDataTable
{
public class Article
{
public Article ()
{
}
private int _modelNumber;
public int ModelNumber
{
get { return _modelNumber; }
set { _modelNumber = value; OnPropertyChanged("ModelNumber"); }
}
private string _modelName;
public string ModelName
{
get { return _modelName; }
set { _modelName = value; OnPropertyChanged("ModelName"); }
}
private decimal _unitCost;
public decimal UnitCost
{
get { return _unitCost; }
set { _unitCost = value; OnPropertyChanged("UnitCost"); }
}
private string _description ;
public string Description
{
get { return _description; }
set { _description = value; OnPropertyChanged("Description"); }
}
#region INotifyPropertyChanged Membres
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
public class ListArticles : ObservableCollection<Article >
{
public Article a;
public ListArticles()
{
a = new Article();
this.Add(a);
}
}
}
And here is the XAML code:
<Window x:Class="WpfDataGridWithDataTable.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfDataGridWithDataTable"
Title="Window1" Height="300" Width="300">
<Grid
Name="gridPanel">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<DataGrid
Grid.Column="0"
Name="dataGrid1"
AutoGenerateColumns="True"
CanUserAddRows="True"
CanUserDeleteRows="True"
CanUserResizeColumns="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"/>
<ListBox
Grid.Column="1"
Name="listBox1"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:Article}">
<StackPanel
Orientation="Horizontal">
<TextBlock
Width="100"
Margin="10"
Background="DarkBlue"
Foreground="White"
FontSize="14"
Text="{Binding ModelNumber}"/>
<TextBlock
Width="100"
Margin="10"
Background="DarkBlue"
Foreground="White"
FontSize="14"
Text="{Binding ModelName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Left"
Width="100"
Name="btnAdd"
Content="Add Item"
Click="btnAdd_Click">
</Button>
<Button
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Right"
Width="100"
Name="btnDelete"
Content="Delete Item"
Click="btnDelete_Click" >
</Button>
</Grid>
</Window>
And the code behind the form:
namespace WpfDataGridWithDataTable
{
/// <summary>
/// Logique d'interaction pour Window1.xaml
/// </summary>
public partial class Window1 : Window
{
private ListArticles myList;
public Window1()
{
InitializeComponent();
myList = new ListArticles();
this.DataContext = myList;
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
myList.Add(new Article());
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
myList.Remove(this.dataGrid1.SelectedItem as Article);
}
}
}
Thanks for your answers.
I finally resolved the problem, but I don't know why it didn't worked. The issue was located in the XAML code when binding the datagrid with the CollectionViewSource. I put:
<DataGrid Name="dataGrid1"
AutoGenerateColumns="True"
CanUserAddRows="True"
CanUserDeleteRows="True"
CanUserResizeColumns="True"
IsSynchronizedWithCurrentItem="True"
Grid.ColumnSpan="2"
ItemsSource="{Binding Source={StaticResource cvsListArticles}}">
The last line was the problem, when I simply put ItemsSource="{Binding}" it finally worked.
Here is the code for those who might have the same problem and want to benefit from it:
code behind data:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfDataGridWithDataTable
{
public class Article
{
public Article()
{
}
private int _modelNumber;
public int ModelNumber
{
get { return _modelNumber; }
set { _modelNumber = value; OnPropertyChanged("ModelNumber"); }
}
private string _modelName;
public string ModelName
{
get { return _modelName; }
set { _modelName = value; OnPropertyChanged("ModelName"); }
}
private decimal _unitCost;
public decimal UnitCost
{
get { return _unitCost; }
set { _unitCost = value; OnPropertyChanged("UnitCost"); }
}
private string _description;
public string Description
{
get { return _description; }
set { _description = value; OnPropertyChanged("Description"); }
}
#region INotifyPropertyChanged Membres
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
public class ListArticles : ObservableCollection<Article>
{
public Article a;
public ListArticles()
{
a = new Article();
this.Add(a);
}
}
}
The XAML code:
<Window x:Class="WpfDataGridWithDataTable.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication18"
Title="Window1" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="480" Width="760">
<Window.Resources>
<CollectionViewSource x:Key="cvsListArticles" Source="Article">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ModelName"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid
Name="gridPanel">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<DataGrid Name="dataGrid1"
AutoGenerateColumns="True"
CanUserAddRows="True"
CanUserDeleteRows="True"
CanUserResizeColumns="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}" Grid.ColumnSpan="2" >
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
<Button
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Left"
Width="100"
Name="btnAdd"
Content="Add Item"
Click="btnAdd_Click">
</Button>
<Button
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Right"
Width="100"
Name="btnDelete"
Content="Delete Item"
Click="btnDelete_Click" >
</Button>
<Button Content="Group" Grid.ColumnSpan="2" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="294,5,0,0" Name="GroupButton" VerticalAlignment="Top" Width="145" Click="GroupButton_Click" />
</Grid>
</Window>
The code behind the form:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfDataGridWithDataTable
{
public partial class Window1 : Window
{
private ListArticles myList;
public Window1()
{
InitializeComponent();
myList = new ListArticles();
this.DataContext = myList;
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
myList.Add(new Article());
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
myList.Remove(this.dataGrid1.SelectedItem as Article);
}
private void GroupButton_Click(object sender, RoutedEventArgs e)
{
ICollectionView cvsListArticles = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
if (cvsListArticles != null && cvsListArticles.CanGroup == true)
{
cvsListArticles.GroupDescriptions.Clear();
cvsListArticles.GroupDescriptions.Add(new PropertyGroupDescription("ModelName"));
}
}
}
}

Categories

Resources