WPF Expander - Only one expander to be expanded at any time - c#

In a WPF list view I create few WPF expanders which inside each expander I have few items. Now I want to only one of the expanders to be expanded each time...meaning all other expanders to be collopsed if one is expanded. Can you please help? Please note that the expanders are created by ItemTemplate !
XMAL :
<UserControl x:Class="DataRetrieval.Views.ParametersView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:local="clr-namespace:DataRetrieval.Views"
d:DesignWidth="400" Height=" 600" Background="White">
<UserControl.Resources>
<DataTemplate x:Key="StringDataTemplate">
<StackPanel Margin="5" >
<TextBlock Text="{Binding Name}" />
<TextBox Text="{Binding SelectedValue, UpdateSourceTrigger=PropertyChanged}" Width="290" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="DateDataTemplate">
<StackPanel Margin="5" >
<ComboBox SelectedIndex="{Binding SelectedValue, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Name}" Width="290" ItemsSource="{Binding UserItems}"/>
<DatePicker SelectedDate="{Binding multitype.datetime}" Width="200"/>
</StackPanel>
</DataTemplate>
<local:ParamControlTemplateSelector x:Key="myParamTemplateSelector" />
</UserControl.Resources>
<Border BorderBrush="LightGray" BorderThickness="1,1,1,1" CornerRadius="8,8,8,8" Margin="20" Background="#FFF3F3F3">
<Grid Margin="5">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ListView x:Name="myListView1" ItemsSource="{Binding Qtables, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="{Binding SelectedIndex}">
<ListView.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}" Width="320" IsExpanded="{Binding IsExpanded}" >
<StackPanel Height="600">
<TextBlock Text="{Binding Name}" />
<ListView x:Name="myListView" ItemsSource="{Binding Params, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemTemplateSelector="{StaticResource myParamTemplateSelector}" SelectedIndex="{Binding CurrentUser, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</ListView>
</StackPanel>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
</Grid>
</Border>
</UserControl>
ViewModel
namespace DataRetrieval.ViewModel
{
class ParametersViewModel:BindableBase
{
public ParametersViewModel()
{
AParams = new WhereParams();
Qtables = new QDatatables();
for (int j = 0; j < 5;j++ )
{
for (int i = 0; i < 10; ++i)
{
int[] numbers;
numbers = new int[3] { 1, 2, 3 };
var parameter = new WhereParam { ID = i, Name = "Name " + i.ToString() };
if (i == 2 || i == 4)
{
parameter.Type = ParamTypeEnum.datetimeType;
}
AParams.Add(parameter);
}
var qtable = new QDatatable { ID=j, Name = j.ToString() + "QTable", Params = AParams};
Qtables.Add(qtable);
}
}
private WhereParam _parameter;
public WhereParam Parameter
{
get { return _parameter; }
set
{
SetProperty(ref _parameter, value);
}
}
private WhereParams _paramas;
public WhereParams AParams
{
get { return _paramas; }
set { SetProperty(ref _paramas, value); }
}
private QDatatables _qtables;
public QDatatables Qtables
{
get { return _qtables; }
set
{
SetProperty(ref _qtables, value);
}
}
}
}

This could be done by changing the Model to handle the IsExpanded
Model for QTables:
public class QDatatables : BindableBase
{
public QDatatables()
{
List.CollectionChanged += List_CollectionChanged;
}
void List_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (var item in e.NewItems)
{
var test = item as QDatatable;
test.PropertyChanged += test_PropertyChanged;
}
}
void test_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsExpanded")
{
var cur = sender as QDatatable;
if (cur.IsExpanded == true)
{
foreach (var item in List)
{
if (item.Name != cur.Name && item.IsExpanded == true)
{
item.IsExpanded = false;
}
}
}
}
}
private ObservableCollection<QDatatable> _list;
public ObservableCollection<QDatatable> List
{
get { return _list ?? (_list=new ObservableCollection<QDatatable>()); }
set { SetProperty(ref _list, value); }
}
}

Related

Databinding between 2 listViews selecting/deselecting records

I have 2 listviews, one that is prepopulated with a list of values and an empty list that should populate based on what is selected/deselected in the first view. I can get the second view to populate when a value is selected but I'm not sure how to remove an item if it's been deselected in the first view. If I do deselect a record it adds a null value to the 2nd list. I'm thinking there is a way around this with if/else statements but I'm thinking there might be a more elegant way to accomplish this.
View Model
private ObservableCollection<string> _firstList;
public ObservableCollection<string> FirstList
{
get => _firstList;
set
{
if (_firstList!= value)
{
_firstList= value;
RaisePropertyChanged(nameof(FirstList));
}
}
}
private string _selectedRecord;
public string SelectedRecord
{
get => _selectedRecord;
set
{
if (_selectedRecord!= value)
{
_selectedRecord= value;
RaisePropertyChanged(nameof(SelectedRecord));
_secondList.Add(_selectedRecord);
}
}
}
private ObservableCollection<string> _secondList=
new ObservableCollection<string>();
public ObservableCollection<string> SecondList
{
get => _secondList;
set
{
if (_secondList!= value)
{
_secondList= value;
RaisePropertyChanged(nameof(SecondList));
}
}
}
XAML -
<ListView ItemsSource="{Binding FirstList}"
SelectedItem="{Binding SelectedRecord}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource
AncestorType=ListViewItem}}"/>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView ItemsSource="{Binding SecondList}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Replace the string with a custom type and handle your logic of adding and removing items in the view model:
public class ListItem : INotifyPropertyChanged
{
public ListItem(string value) =>
Value = value;
public string Value { get; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; RaisePropertyChanged(nameof(IsSelected)); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
View Model:
public ViewModel()
{
InitializeComponent();
DataContext = this;
FirstList.CollectionChanged += (s, e) =>
{
if (e.NewItems != null)
foreach (var newItem in e.NewItems.OfType<INotifyPropertyChanged>())
newItem.PropertyChanged += OnItemIsSelectedChanged;
if (e.OldItems != null)
foreach (var oldIrem in e.OldItems.OfType<INotifyPropertyChanged>())
oldIrem.PropertyChanged -= OnItemIsSelectedChanged;
};
FirstList.Add(new ListItem("a"));
FirstList.Add(new ListItem("b"));
FirstList.Add(new ListItem("c"));
}
private void OnItemIsSelectedChanged(object sender, PropertyChangedEventArgs e)
{
ListItem listItem = (ListItem)sender;
if (listItem.IsSelected)
{
if (!SecondList.Contains(listItem))
SecondList.Add(listItem);
}
else
SecondList.Remove(listItem);
}
public ObservableCollection<ListItem> FirstList { get; } =
new ObservableCollection<ListItem>();
public ObservableCollection<ListItem> SecondList { get; }
= new ObservableCollection<ListItem>();
View:
<ListView ItemsSource="{Binding FirstList}" SelectedItem="{Binding SelectedRecord}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}"/>
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView ItemsSource="{Binding SecondList}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is MVVM in a nutshell.

WPF: Bindings break when CollectionChanges - why?

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:

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.

Properties are null when calling a command method

When RefreshServices() is called, both SelectedComputerand SelectedCustomerare null, even though they are not when I actually select them.
Why?
/edit: Forgot to post the buttons' declarations in the XAML. It's updated now.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
MainViewModel _main = new MainViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _main;
}
}
XAML:
<Window.Resources>
<vm:MainViewModel x:Key="viewModel" />
</Window.Resources>
<Border Margin="10">
<Grid>
<!-- Comboboxes -->
<GroupBox Header="Computer" Grid.Column="0" Grid.ColumnSpan="4" Margin="0 0 4 4">
<ComboBox ItemsSource="{Binding ComputerNames}"
SelectedItem="{Binding SelectedComputer}"
IsSynchronizedWithCurrentItem="True"
SelectedIndex="0"/>
</GroupBox>
<GroupBox Header="Customer" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" Margin="0 0 4 4" >
<ComboBox ItemsSource="{Binding CustomerNames}"
SelectedItem="{Binding SelectedCustomer}"
IsSynchronizedWithCurrentItem="True"
SelectedIndex="0"/>
</GroupBox>
<!-- Main list -->
<DataGrid x:Name="dataGrid" Grid.Row="2" Grid.ColumnSpan="8"
ItemsSource="{Binding Path=Services, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
AutoGenerateColumns="False"
IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="*" Binding="{Binding DisplayName}"/>
<DataGridTextColumn Header="Status" Width="*" Binding="{Binding Status}" />
<DataGridTextColumn Header="Machine Name" Width="*" Binding="{Binding MachineName}" />
</DataGrid.Columns>
</DataGrid>
<!-- Buttons-->
<Button Grid.Row="5" Grid.Column="0" Margin="0 4 4 4" Content="Start"
Command="{Binding Path=StartServiceCommand, Source={StaticResource viewModel}}"
CommandParameter="{Binding SelectedItems, ElementName=dataGrid}"/>
<Button Grid.Row="5" Grid.Column="1" Margin="4 4 0 4" Content="Stop"
Command="{Binding Path=StopServiceCommand, Source={StaticResource viewModel}}"
CommandParameter="{Binding SelectedItems, ElementName=dataGrid}"/>
<Button Grid.Row="5" Grid.Column="3" Margin="4 4 0 4" Content="Refresh"
Command="{Binding Path=RefreshServicesCommand, Source={StaticResource viewModel}}" />
</Grid>
</Border>
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
#region INotify methods
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
#endregion
/*---------------------------- C U S T O M E R S -----------------------------*/
#region Customer Properties
private string _selectedCustomer;
private ObservableCollection<string> _customerNames;
public string SelectedCustomer
{
get => _selectedCustomer;
set
{
SetField(ref _selectedCustomer, value);
Services = Utils.UpdatedServices(SelectedComputer, SelectedCustomer);
}
}
public ObservableCollection<string> CustomerNames
{
get => _customerNames;
set
{
SetField(ref _customerNames, value);
Services = Utils.UpdatedServices(SelectedComputer, SelectedCustomer);
}
}
#endregion
/*---------------------------- S E R V I C E S -----------------------------*/
#region Services Properties
private ObservableCollection<ServiceController> _services;
private ObservableCollection<ServiceController> _selectedServices;
public ObservableCollection<ServiceController> SelectedServices
{
get => _selectedServices;
set => SetField(ref _selectedServices, value);
}
public ObservableCollection<ServiceController> Services
{
get => _services;
set => SetField(ref _services, value);
}
#endregion
/*---------------------------- C O M P U T E R S -----------------------------*/
#region Computer Properties
private string _selectedComputer;
private ObservableCollection<string> _computerNames;
public string SelectedComputer
{
get => _selectedComputer;
set
{
SetField(ref _selectedComputer, value);
CustomerNames = Utils.UpdatedCustomerNames(SelectedComputer);
}
}
public ObservableCollection<string> ComputerNames
{
get => _computerNames;
set => SetField(ref _computerNames, value);
}
#endregion
/*---------------------------- C O M M A N D S -----------------------------*/
#region Commands
public StartServiceCommand StartServiceCommand { get; set; }
public void StartService(ObservableCollection<ServiceController> serviceControllers)
{
try
{
foreach(var service in serviceControllers)
if (service.Status != ServiceControllerStatus.Running)
service.Start();
RefreshServices();
}
catch (Exception ex) { }
}
public StopServiceCommand StopServiceCommand { get; set; }
public void StopService(ObservableCollection<ServiceController> serviceControllers)
{
try
{
foreach (var service in serviceControllers)
if (service.Status != ServiceControllerStatus.Stopped &&
service.Status != ServiceControllerStatus.StopPending)
service.Stop();
RefreshServices();
}
catch (Exception ex) { }
}
public RefreshServicesCommand RefreshServicesCommand { get; set; }
public void RefreshServices()
{
Services = Utils.UpdatedServices(SelectedComputer, SelectedCustomer);
}
#endregion
public MainViewModel()
{
#region Initialize
_services = new ObservableCollection<ServiceController>();
_computerNames = new ObservableCollection<string>();
_customerNames = new ObservableCollection<string>();
_selectedComputer = _customerNames.FirstOrDefault();
_selectedServices = new ObservableCollection<ServiceController>();
ComputerNames = new ObservableCollection<string> { Environment.MachineName, Environment.MachineName };
StartServiceCommand = new StartServiceCommand(this);
StopServiceCommand = new StopServiceCommand(this);
RefreshServicesCommand = new RefreshServicesCommand(this);
#endregion
}
}
You created two instances of MainViewModel. Whenever you see "everything in my viewmodel is null even though, at the same time, it isn't" on Stack Overflow, look for duplicate viewmodel instantiations in the XAML and the constructor.
You're getting the Commands, inexplicably, from a throwaway copy in Window.Resources that's used for nothing else. Don't do that. Delete Source={StaticResource viewModel} from those bindings, and I think they should work as they are.
Grid is a direct child of the Window and doesn't have its own DataContext, so this should be fine. Let me know if not. We can make it work without too much trouble.
<Button Grid.Row="5" Grid.Column="0" Margin="0 4 4 4" Content="Start"
Command="{Binding StartServiceCommand}"
CommandParameter="{Binding SelectedItems, ElementName=dataGrid}"/>
<Button Grid.Row="5" Grid.Column="1" Margin="4 4 0 4" Content="Stop"
Command="{Binding StopServiceCommand}"
CommandParameter="{Binding SelectedItems, ElementName=dataGrid}"/>
<Button Grid.Row="5" Grid.Column="3" Margin="4 4 0 4" Content="Refresh"
Command="{Binding RefreshServicesCommand}" />

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

Categories

Resources