I have created an UserControl for reusability in my program:
GroupPanel.xaml:
<UserControl x:Class="View.UserControls.GroupPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"assembly=ViewModel"
Name="root">
<DockPanel>
<DataGrid DockPanel.Dock="Top"
AutoGenerateColumns="False"
CanUserAddRows="True"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
DataContext="{Binding ElementName=root}"
ItemsSource="{Binding DataGridItemSource}"
Name="mainGrid">
<DataGrid.Columns>
<DataGridTextColumn Header="Nr/Unit"
Binding="{Binding Nr}" />
<DataGridTextColumn Header="Text"
Binding="{Binding Text}" />
<DataGridTextColumn Header="Comment"
Binding="{Binding Comment}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="Delete" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
and a Dependency Property to set the DataGrid's ItemsSource:
GroupPanel.xaml.cs:
public static readonly DependencyProperty DataGridItemSourceProperty =
DependencyProperty.Register("DataGridItemSource", typeof(IEnumerable), typeof(GroupPanel),
new PropertyMetadata(null));
public IEnumerable DataGridItemSource
{
get { return (IEnumerable)GetValue(DataGridItemSourceProperty); }
set { SetValue(DataGridItemSourceProperty, value); }
}
I have a window where I need this UserControl 2 times with different ItemsSources to bind. But both Sources have the same properties up to first property. The sources are:
GroupNumber:
Number, int
Text, string
Comment, string
GroupUnit:
Unt, int
Text, string
Comment, string
The behaviour is the same for both of them. Is there a way to set the 1 DataGrid column's binding depending on the ItemsSource?
I have tried something like this:
void GroupPanel_Loaded(object sender, RoutedEventArgs e)
{
var type = DataGridItemSource.GetType();
if(type.FullName.Contains("GroupNumber"))
{
}
}
But I don't know how to set the columns binding inside the if...
Thank you in advance
You can add another dependecy property to your GroupPanel usercontrol:
public string FirstColumnBindingPropertyName
{
get { return (string)GetValue(FirstColumnBindingPropertyNameProperty); }
set { SetValue(FirstColumnBindingPropertyNameProperty, value); }
}
public static readonly DependencyProperty FirstColumnBindingPropertyNameProperty =
DependencyProperty.Register("FirstColumnBindingPropertyName", typeof(string), typeof(GroupPanel), new UIPropertyMetadata(null, new PropertyChangedCallback(OnFirstColumnBindingPropertyNameChanged)));
private static void OnFirstColumnBindingPropertyNameChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
GroupPanel groupPanel = (GroupPanel)dependencyObject;
DataGridTextColumn dataGridTextColumn = groupPanel.mainGrid.Columns[0] as DataGridTextColumn;
if (args.NewValue == null)
{
dataGridTextColumn.Binding = null;
}
else
{
dataGridTextColumn.Binding = new Binding(Convert.ToString(args.NewValue));
}
}
Then in your window you will have:
<local:GroupPanel FirstColumnBindingPropertyName="Number" />
<local:GroupPanel FirstColumnBindingPropertyName="Unt" />
You can use a DataTemplateSelector. Please refer below code.
<Window x:Class="DataTemplateSelector_Learning.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataTemplateSelector_Learning"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="GroupNumber">
<TextBlock Text="{Binding Number}"/>
</DataTemplate>
<DataTemplate x:Key="GroupUnit">
<TextBlock Text="{Binding Unit}" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<DataGrid DockPanel.Dock="Top"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
Name="numGrid">
<DataGrid.Columns>
<DataGridTemplateColumn Header="GroupNumber">
<DataGridTemplateColumn.CellTemplateSelector>
<local:GroupTemplateSelector
GroupNumber="{StaticResource GroupNumber}"
GroupUnit="{StaticResource GroupUnit}"/>
</DataGridTemplateColumn.CellTemplateSelector>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text"
Binding="{Binding Text}" />
<DataGridTextColumn Header="Comment"
Binding="{Binding Comment}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="Delete" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid DockPanel.Dock="Top"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
Name="unitGrid">
<DataGrid.Columns>
<DataGridTemplateColumn Header="GroupUnit">
<DataGridTemplateColumn.CellTemplateSelector>
<local:GroupTemplateSelector
GroupNumber="{StaticResource GroupNumber}"
GroupUnit="{StaticResource GroupUnit}"/>
</DataGridTemplateColumn.CellTemplateSelector>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text"
Binding="{Binding Text}" />
<DataGridTextColumn Header="Comment"
Binding="{Binding Comment}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="Delete" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<GroupNumber> lstNum = new ObservableCollection<GroupNumber>();
lstNum.Add(new GroupNumber() { Number=1,Text="Test",Comment="Comment"});
numGrid.ItemsSource = lstNum;
ObservableCollection<Groupunit> lstUnit = new ObservableCollection<Groupunit>();
lstUnit.Add(new Groupunit() { Unit = 2, Text = "Test", Comment = "Comment" });
unitGrid.ItemsSource = lstUnit;
}
}
public class GroupTemplateSelector : DataTemplateSelector
{
public DataTemplate GroupNumber
{ get; set; }
public DataTemplate GroupUnit
{ get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is GroupNumber)
{
return GroupNumber;
}
else if (item is Groupunit)
{
return GroupUnit;
}
else
return base.SelectTemplate(item, container);
}
}
public class GroupNumber
{
private int number;
public int Number
{
get { return number; }
set { number = value; }
}
private string text;
public string Text
{
get { return text; }
set { text = value; }
}
private string comment;
public string Comment
{
get { return comment; }
set { comment = value; }
}
}
public class Groupunit
{
private int unit;
public int Unit
{
get { return unit; }
set { unit = value; }
}
private string text;
public string Text
{
get { return text; }
set { text = value; }
}
private string comment;
public string Comment
{
get { return comment; }
set { comment = value; }
}
}
Related
I am making a datagrid inside a window, where two columns are DockPanels with a TextBlock and a Button. When a user selects a file/folder, the TextBlock receives the value and displays it. However, it does not reflect it right away in my case. When I double click on the TextBlock, it suddenly shows. What am I missing here?
XAML
<DataGrid Name="copierDataGrid" Margin="30,50,30,100"
ItemsSource="{Binding SelectedAsm}" SelectedItem="{Binding Selected}" AutoGenerateColumns="False" CanUserResizeColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Prefix" Width="70" Binding="{Binding prefCol, TargetNullValue=''}"/>
<DataGridTemplateColumn Width="235" CanUserResize="False" Header="Select File">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel DockPanel.Dock="Top">
<TextBlock Name="fileLocColtxtBox" Width="185" Margin="0,0,0,0" TextWrapping="Wrap" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Text="{Binding fileLocCol, TargetNullValue=''}"/>
<Button x:Name="brwsFileBtn" Content="..." Width="30" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Click="brwsFileBtn_Click"/>
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Suffix" Width="70" Binding="{Binding suffCol, TargetNullValue=''}"/>
<DataGridTemplateColumn Header="Target location" Width="*" CanUserResize="False" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel DockPanel.Dock="Top">
<TextBlock Name="destLocColtxtBox" Width="180" Margin="0,0,0,0" TextWrapping="Wrap" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Text="{Binding destLocCol, TargetNullValue=''}"/>
<Button x:Name="brwsFileBtn" Content="Browse" Width="50" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Click="brwsFolderBtn_Click"/>
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
XAML.CS
public itemAsm Selected { get; set; }
public ObservableCollection<itemAsm> SelectedAsm { get; set; }
public unitCopierFrm()
{
InitializeComponent();
SelectedAsm = new ObservableCollection<itemAsm>();
DataContext= this;}
public class itemAsm
{
public string prefCol { get; set; }
public string fileLocCol { get; set; }
public string suffCol { get; set; }
public string destLocCol { get; set; }
}
}private void brwsFolderBtn_Click(object sender, RoutedEventArgs e)
{
if (Selected !=null)
{
System.Windows.Forms.FolderBrowserDialog dlgFolderBrowser = new System.Windows.Forms.FolderBrowserDialog();
dlgFolderBrowser.Description = "Select the destination folder";
dlgFolderBrowser.RootFolder = System.Environment.SpecialFolder.MyComputer;
dlgFolderBrowser.ShowDialog();
string dirPath = dlgFolderBrowser.SelectedPath;
//if (dlgFolderBrowser.ShowDialog == Windows)
if (dirPath != null)
{
Selected.destLocCol = dirPath;
}
}
}
itemAsm should implement INotifyPropertyChanged and raise the PropertyChanged event to notify the view whenever the data-bound property has been set to a new value:
public class itemAsm : INotifyPropertyChanged
{
private string _prefCol;
public string prefCol
{
get { return _prefCol; }
set { _prefCol = value; NotifyPropertyChanged(); }
}
private string _fileLocCol;
public string fileLocCol
{
get { return _fileLocCol; }
set { _fileLocCol = value; NotifyPropertyChanged(); }
}
private string _suffCol;
public string suffCol
{
get { return _suffCol; }
set { _suffCol = value; NotifyPropertyChanged(); }
}
private string _destLocCol;
public string destLocCol
{
get { return _destLocCol; }
set { _destLocCol = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
You may also want to consider renaming the properties according to the C# naming conventions.
I've defined a custom data-grid whose columns are made up of text-boxes, combo-boxes, and buttons. I've also populated the data on each combo-box by using XAML Binding. Now, I'd like to read the of each selected combobox value and textbox value from the data-grid. But I don't find any way to read it. I've researched on forums and find solutions related to DataGridView but not much for DataGrid. Here's my sample code where I'm defining the custom data-grid and how I'm populating the data in it.
public class Data
{
public List<string> Disciplines { get; set; }
public List<string> Members { get; set; }
public List<string> ActionType { get; set; }
public Data()
{
this.Disciplines = new List<string>
{
"Architecture", "Mechanical", "Structure"
};
this.Members = new List<string>
{
"Ali", "Mubashar", "Muffassir", "Nitin"
};
this.ActionType = new List<string>
{
"Take Action",
"For Information"
};
}
}
public MainWindow()
{
InitializeComponent();
datagrid_additionalinfo.Items.Add(new Data());
}
<DataGrid Name="datagrid_additionalinfo" Margin="20,0,20,0"
IsReadOnly="False" SelectionMode="Single" CanUserAddRows="True"
AutoGenerateColumns="False" SelectionUnit="Cell" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Comment" Width="*" MinWidth="130">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Width="235"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Discipline" Width="100" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="200" ItemsSource="{Binding Disciplines}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Members" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="200" ItemsSource="{Binding Members}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Action Type" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="200" ItemsSource="{Binding ActionType}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Here's how the custom data-grid will look like :
Would appreciate any help. Thank you
in addition to a collection property public List<string> Disciplines { get; set; } declare a property for selected item
public string SelectedDiscipline { get; set; }
and bind it to ComboBox
<ComboBox Width="200"
SelectedIndex="0"
SelectedItem="{Binding SelectedDiscipline, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Disciplines}"/>
and do the same for other properties
this way you can access all values without working with DataGridRow cells directly
I have seen that there is basic understanding problem here. You mix behind code with MVVM structure. Instead of that code;
datagrid_additionalinfo.Items.Add(new Data());
try to use use ObservableCollection<Data> as ItemSource for the DataGrid in your ViewModel. Use your ViewModel as DataContext for your View. After that you can bind the SelectedItems.
Here is the code;
MainWindow.xaml
<Window x:Class="DataGridComboBox.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:DataGridComboBox"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid Name="datagrid_additionalinfo" Margin="20,0,20,0"
ItemsSource="{Binding DataItems}"
SelectedItem="{Binding SelectedDataRow, UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="False" SelectionMode="Single" CanUserAddRows="True"
AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Comment" Width="*" MinWidth="130">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Width="235"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Discipline" Width="100" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="200" ItemsSource="{Binding Disciplines}"
SelectedItem="{Binding DataContext.SelectedDisipline, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Members" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="200" ItemsSource="{Binding Members}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Action Type" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="200" ItemsSource="{Binding ActionType}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
**MainWindowViewModel**
using System.Collections.ObjectModel;
using System.ComponentModel;
...
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Data> m_DataItems;
public ObservableCollection<Data> DataItems
{
get { return m_DataItems; }
set { m_DataItems = value; }
}
private Data m_SelectedDataRow;
public Data SelectedDataRow
{
get { return m_SelectedDataRow; }
set { m_SelectedDataRow = value; }
}
private string m_SelectedDisipline;
public string SelectedDisipline
{
get { return m_SelectedDisipline; }
set { m_SelectedDisipline = value; }
}
public MainWindowViewModel()
{
m_DataItems = new ObservableCollection<Data>();
//Fill Items
Data data;
for (int i = 0; i < 10; i++)
{
data = new Data();
m_DataItems.Add(data);
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The problem: the DataGrid display is empty, however I have infos, and my DataGrid received the information but still empty!
My XAML:
<DataGrid x:Name="dataGrid" Grid.Row="1" ItemsSource="{Binding ViewList}"
CanUserAddRows="False" AlternationCount="2"
AlternatingRowBackground="Blue">
<DataGrid.Columns>
<DataGridTextColumn Header="View" Binding="{Binding id}"
Width="2*" IsReadOnly="True" />
<DataGridTemplateColumn Header="Is Enabled" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding isEnabled, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
My ViewModel:
public ConfigRoleModel()
{
_viewList = new ObservableCollection<ViewList>(WCFclient.getListOfViews(1));
}
private ObservableCollection<ViewList> _viewList;
public ObservableCollection<ViewList> ViewList
{
get { return _viewList; }
set
{
_viewList = value;
OnPropertyChanged("ViewList");
}
}
ViewList class:
public class ViewList
{
public int id;
public string description;
public string code;
}
This is the result:
How can I fix it ?
By looking at your code:
You should define public property in your ViewList class for binding to work.
set Datacontext to your viewModel.
No isEnabled property in your DataContext
Your ViewList Class should look like this:
public class ViewList
{
public int Id { get; set; }
public bool IsEnabled { get; set; }
...
}
and your Xaml:
<DataGrid x:Name="dataGrid" Grid.Row="1" ItemsSource="{Binding ViewList}"
CanUserAddRows="False" AlternationCount="2" AlternatingRowBackground="Blue" >
<DataGrid.Columns>
<DataGridTextColumn Header="View" Binding="{Binding Id}" Width="2*" IsReadOnly="True" />
<DataGridTemplateColumn Header="Is Enabled" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsEnabled , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And in your view code behind or in your xaml itself:
Set your DataContext to your ViewModel
Fields are not valid targets for WPF bindings. You should use a property instead.
public class ViewList {
public int Id { get; set; }
public string Description { get; set; }
public string Code { get; set; }
public bool IsEnabled { get; set; }
}
Make sure that your View List class implements INotifyPropertyChanged
public class ViewList : INotifyPropertyChanged
{
private int _id;
public int id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged(new PropertyChangedEventArgs("id"));
}
}
private string _description;
public string description
{
get { return _description; }
set
{
if((value as string) != null)
{
_description = value;
OnPropertyChanged(new PropertyChangedEventArgs("description"));
}
}
}
private string _code;
public string code
{
get { return _code; }
set
{
_code = value;
OnPropertyChanged(new PropertyChangedEventArgs("code"));
}
}
private bool _isEnabled;
public bool isEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged(new PropertyChangedEventArgs("isEnabled"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Your ViewModel does not need to implement INotifyPropertyChanged if you are just wanting to display data from the ObservableCollection. Here is my ViewModel:
public class MainWindowVM
{
private ObservableCollection<ViewList> _MyList;
public ObservableCollection<ViewList> MyList
{
get { return _MyList; }
set
{
if(value != null)
{
_MyList = value;
}
}
}
public MainWindowVM()
{
_MyList = new ObservableCollection<WpfDataGridTest.ViewList>();
_MyList.Add(new WpfDataGridTest.ViewList() { id = 1, code = "C101", description = "test1", isEnabled = true });
_MyList.Add(new WpfDataGridTest.ViewList() { id = 2, code = "C102", description = "test2", isEnabled = false });
_MyList.Add(new WpfDataGridTest.ViewList() { id = 3, code = "C103", description = "test3", isEnabled = true });
}
}
Here is my Window's XAML
<Window x:Class="WpfDataGridTest.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:WpfDataGridTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
DataContext="{StaticResource MainWindowVM}">
<Grid>
<DataGrid x:Name="dataGrid" HorizontalAlignment="Left" Margin="33,34,0,0" VerticalAlignment="Top" Height="236" Width="444"
CanUserAddRows="False" AlternationCount="2" AlternatingRowBackground="Blue" AutoGenerateColumns="False" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding MyList}">
<DataGrid.Columns>
<DataGridTextColumn Header="View" Binding="{Binding id}" Width="2*" IsReadOnly="True" />
<DataGridTemplateColumn Header="Is Enabled" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding isEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="44,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"
Text="{Binding Path=CurrentItem.description, ElementName=dataGrid}"/>
</Window>
This example shows the 3 rows as expected for me using VS 2015. as you can see here:
Note: I renamed your ViewList member of the ViewModel to MyList because I don't like having a member be the same name of a class as it can make things confusing.
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
my checkbox checked event is totally not trigger at all. here is my datagrid code. How should i trigger Checked event in wpf mvvm.
<Window x:Class="EmployeeManager.View.DataGridDownload"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Title="DataGridDownload" Height="600" Width="790">
<Grid>
<DataGrid HorizontalAlignment="Left" ItemsSource="{Binding TransView}" AutoGenerateColumns="False" Margin="10,62,0,0" VerticalAlignment="Top" Height="497" Width="762">
<DataGrid.Columns>
<DataGridTextColumn Header="caseRefNo" Binding="{Binding caseRefNo}" />
<DataGridTextColumn Header="subjMatr" Binding="{Binding subjMatr}" />
<DataGridTextColumn Header="Download %" Binding="{Binding incValue}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
Content="Please Select" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding CheckCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding incValue,UpdateSourceTrigger=PropertyChanged}" Background="Red" Foreground="White" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Label Content="{Binding UpdatePercentage}" HorizontalAlignment="Left" Background="Blue" Foreground="White" Margin="10,10,0,0" VerticalAlignment="Top" Width="338" Height="30">
</Label>
<Button Content="Button" HorizontalAlignment="Left" Margin="672,20,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
Here is my view model
public class DataGridDownloadViewModel:BindableBase{
public ObservableCollection<tblTransaction> TransList
{ get; private set; }
public DispatcherTimer dispatchTimer = new DispatcherTimer();
public CollectionView TransView
{ get; private set; }
public DelegateCommand<object> CheckCommand { get; set; }
private String _UpdatePer;
public String UpdatePercentage
{
get { return _UpdatePer; }
set { SetProperty(ref _UpdatePer, value); }
}
private string _caseId;
public string CaseID
{
get { return _caseId; }
set { SetProperty(ref _caseId, value); }
}
private string _isChecked;
public string isChecked
{
get { return _isChecked; }
set { SetProperty(ref _isChecked, value); }
}
private bool CanExecute(object args)
{
return true;
}
private void CheckBoxChecker(object args)
{
//Should Work Here
// Totally not coming to this function
}
public DataGridDownloadViewModel(List<tblTransaction> model)
{
CheckCommand = new DelegateCommand<object>(CheckBoxChecker, CanExecute);
dispatchTimer.Interval = TimeSpan.FromMilliseconds(3000);
dispatchTimer.Tick += dispatchTimer_Tick;
BackGroundThread bgT = Application.Current.Resources["BackGroundThread"] as BackGroundThread;
bgT.GetPercentChanged += (ss, ee) =>
{
UpdatePercentage = bgT.local_percentage.ToString();
};
bgT.GetCaseID += (ss, ee) =>
{
CaseID = bgT.local_caseRef;
};
TransList =new ObservableCollection<tblTransaction>(model);
TransView = GetTransCollectionView(TransList);
TransView.Filter = OnFilterTrans;
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var cancellationTokenSource = new CancellationTokenSource();
dispatchTimer.Start();
}
private void dispatchTimer_Tick(object sender, EventArgs e)
{
UpdateDataGrid();
}
public void UpdateDataGrid()
{
foreach (tblTransaction tran in TransList)
{
if (tran.caseRefNo == CaseID)
{
tran.incValue = int.Parse(UpdatePercentage);
}
else
{
tran.incValue = tran.incValue;
}
}
TransView.Refresh();
}
bool OnFilterTrans(object item)
{
var trans = (tblTransaction)item;
return true;
}
public CollectionView GetTransCollectionView(ObservableCollection<tblTransaction> tranList)
{
return (CollectionView)CollectionViewSource.GetDefaultView(tranList);
}
}
Is it the correct way of doing it? why checkcommand is not trigger?
Edited
here is my model :
public class tblTransaction
{
public string caseRefNo { get;set;}
public string subjMatr { get; set; }
public int incValue { get; set; }
public DateTime? longTime { get; set; }
public bool IsSelected { get; set; }
}
Thanks
The code below worked for me without using the interactivity library like the answer above, just change the AncestorType property on the Command binding declaration according to your case, for me, I was using a UserControl, on the example above they used Window.
Good Luck!
<DataGridTemplateColumn Width="auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="select" HorizontalAlignment="Center"
Command="{Binding DataContext.CheckCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
IsChecked="{Binding Path=Selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Try the following:
<i:InvokeCommandAction Command="{Binding DataContext.CheckCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" />