I am working on wpf mvvm pattern.
I have designed multiple UserControls for different checkboxes. On selection of a checkbox, the corresponding UserControl should get loaded in the right side.
For a single checkbox, I have added the UserControl in MainView.xaml as this:
<StackPanel>
<UserControl Name="CCS01" ScrollViewer.CanContentScroll="True" Margin="5" >
<local:CCS01 HorizontalAlignment="Left"></local:CCS01>
</UserControl>
</StackPanel>
I have stored the list of Checkboxes in a datagrid in a different UserControl like this
<DataGrid Width="150" Grid.Row="0" Background="LightGray" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" Name="dataGridCustomers" ItemsSource="{Binding Path=UsecaseListItems}" CanUserResizeColumns="False" CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="40" Header="Select" Binding="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}">
</DataGridCheckBoxColumn>
<DataGridTextColumn Width="85" Binding="{Binding Path=UsecaseName}" Header="UsecaseName" IsReadOnly="True" >
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Black"></Setter>
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
The viewmodel class is like this:
[![private string _usecaseName;
public string UsecaseName
{
get { return _usecaseName; }
set
{
_usecaseName = value != null ? value.Trim() : null;
OnPropertyChanged("UsecaseName");
}
}
private string _description;
public string Description
{
get { return _description; }
set
{
_description = value != null ? value.Trim() : null;
OnPropertyChanged("Description");
}
}
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
private bool _AllSelected;
public bool AllSelected
{
get { return _AllSelected; }
set
{
_AllSelected = value;
foreach (var reportListItemModel in UsecaseListItems)
{
reportListItemModel.IsSelected = this._AllSelected;
}
OnPropertyChanged("AllSelected");
}
}
private ObservableCollection<UseCase> _usecaseListItems = new ObservableCollection<UseCase>();
public ObservableCollection<UseCase> UsecaseListItems
{
get { return _usecaseListItems; }
set {
_usecaseListItems = value;
OnPropertyChanged("UsecaseListItems");
}
}][1]][1]
In short, I want to divide the page into two columns, on the left I have a list of UserControls and right I want to view the selected UserControl (only one can be selected) and how to bind the selected checkbox with the respective UserControl in ViewModel class.
For reference, I am adding the image of UI here:
You try Something like this :
*.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Column="0" Background="LightGray" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" ItemsSource="{Binding Path=UsecaseListItems}"
Name="dataGridCustomers" CanUserResizeColumns="False" CanUserResizeRows="False"
SelectionChanged="dataGridCustomers_SelectionChanged">
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="auto" Header="Select" Binding="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}">
</DataGridCheckBoxColumn>
<DataGridTextColumn Width="auto" Binding="{Binding UsecaseName}" Header="UsecaseName" IsReadOnly="True" >
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Black"></Setter>
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<Border Grid.Column="1" BorderThickness="1" BorderBrush="Gray">
<Grid Grid.Column="1" x:Name="HostGrid" Margin="5">
</Grid>
</Border>
</Grid>
*.cs (code behind)
private void dataGridCustomers_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var SelectedItem = dataGridCustomers.SelectedItem;
string UserControlName = ((UseCase)SelectedItem).UsecaseName;
Assembly ass = Assembly.GetExecutingAssembly();
foreach (var item in ass.GetTypes())
{
if (item.Name == UserControlName)
{
UserControl uc = (UserControl)Activator.CreateInstance(item,null);
HostGrid.Children.Add(uc);
}
}
}
If you can remove the checkbox, it would be better. Because the CheckBox allows multi-selection
Create a class MyControl with 2 properties string Name (will be binded to your datagrid) and UserControl Control it will be your control.
Create a list of your controls.
In your ViewModel create a MyControl SelectedControl property, this will tell you what's the selected controls, and bind it to the datagrid
In your View add a ContentControl and bind it to: SelectedControl.Control
Related
My project consists of a Treeview with a Datagrid as a child. The parent in the Treeview has a checkbox, and every row in the datagrid also has a check box. My goal is to bind the checkboxes together, so that when the parent checkbox is checked, all of the datagrid rows are checked as well. When the datagrid rows are checked I would like the parent checkbox to be updated based of the datagrid row checked values.
Parent checkbox IsChecked = true when all of the datagrid row checkboxes are true, null if only some are, and false if no rows are checked.
The problem I'm facing is that my dependency properties aren't updating and the PropertyChangedCallback is not getting called.
This is my Checkbox_Helper class in which the IsChecked properties of both the parent and the datagrid row checkboxes are bound to.
public class CheckBox_Helper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(CheckBox_Helper),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Parent && ((bool?)e.NewValue).HasValue)
{
foreach (Child s in (d as Parent).SheetList[0].SheetChildList)
{
if (s.IsSizeCompatible)
{
SetIsChecked(s, (bool?)e.NewValue);
}
}
}
if (d is Child)
{
AddToCheckedElements(d as Child);
int _checked = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Where(x => GetIsChecked(x) == true).Count();
int _unchecked = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Where(x => GetIsChecked(x) == false).Count();
int _total = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Count();
if ((d as Child).MegaParent.SelectedRowList != null && (d as Child).MegaParent.SelectedRowList.Count > 0)
{
(d as Child).MegaParent.SelectedRowList.Where(i => GetIsChecked(i) != GetIsChecked(d as Child)).ToList().ForEach(i => CheckBox_Helper.SetIsChecked(i, GetIsChecked(d as Child)));
}
if (_checked == 0)
{
(d.GetValue(ParentProperty) as Parent).HasCheckedItems = false;
}
else
{
(d.GetValue(ParentProperty) as Parent).HasCheckedItems = true;
}
(d.GetValue(ParentProperty) as Parent).CheckedRatio = $"{_checked}/{_total}";
if (_unchecked > 0 && _checked > 0)
{
CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, null);
return;
}
if (_checked > 0)
{
CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, true);
return;
}
CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, false);
}
}
public static readonly DependencyProperty ParentProperty = DependencyProperty.RegisterAttached("Parent", typeof(Parent), typeof(CheckBox_Helper));
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element.SetValue(CheckBox_Helper.IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element.GetValue(CheckBox_Helper.IsCheckedProperty);
}
// Set and Get Parent are unused since they are hardcoded categories
public static void SetParent(DependencyObject element, object Parent)
{
element.SetValue(CheckBox_Helper.ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return (object)element.GetValue(CheckBox_Helper.ParentProperty);
}
private static void AddToCheckedElements(Child child)
{
ObservableCollection<Child> tempList = child.MegaParent.CheckedElements;
if (!child.MegaParent.CheckedElements.Contains(child) && GetIsChecked(child) == true)
{
tempList.Add(child);
child.MegaParent.CheckedElements = tempList;
}
else if (child.MegaParent.CheckedElements.Contains(child) && GetIsChecked(child) == false)
{
tempList.Remove(child);
child.MegaParent.CheckedElements = tempList;
}
}
}
This is the code for the Parent class:
public class Parent : DependencyObject
{
public string Name { get; set; }
public ObservableCollection<ChildList> SheetList { get; set; }
public static readonly DependencyProperty HasCheckedItemsProperty =
DependencyProperty.Register("HasCheckedItems", typeof(bool), typeof(Parent), new UIPropertyMetadata(false));
public bool HasCheckedItems
{
get { return (bool)GetValue(HasCheckedItemsProperty); }
set { SetValue(HasCheckedItemsProperty, value); }
}
public static readonly DependencyProperty CheckedRatioProperty =
DependencyProperty.Register("CheckedRatio", typeof(string), typeof(Parent), new UIPropertyMetadata($"0/0"));
public string CheckedRatio
{
get { return (string)GetValue(CheckedRatioProperty); }
set { SetValue(CheckedRatioProperty, value); }
}
public Parent(string name)
{
Name = name;
SheetList = new ObservableCollection<ChildList>();
}
}
This is the code for the Child class:
(Note: The MegaParent property is the viewmodel)
public class Child : DependencyObject
{
public ViewSheet Sheet { get; set; }
public string Name { get; set; }
public string Number { get; set; }
private string _paperSize = "";
public string PaperSize
{
get { return _paperSize; }
set { _paperSize = value; }
}
public static readonly DependencyProperty IsSizeCompatibleProperty =
DependencyProperty.Register("IsSizeCompatible", typeof(bool), typeof(Child), new UIPropertyMetadata(true));
public bool IsSizeCompatible
{
get { return (bool)GetValue(IsSizeCompatibleProperty); }
set { SetValue(IsSizeCompatibleProperty, value); }
}
public PrintSheets_ViewModel MegaParent { get; set; }
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(Child), new UIPropertyMetadata(false));
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); AddToSelectedList(); }
}
private void AddToSelectedList()
{
ObservableCollection<Child> tempList = MegaParent.SelectedRowList;
if (!MegaParent.SelectedRowList.Contains(this) && (bool)GetValue(IsSelectedProperty) == true)
{
tempList.Add(this);
MegaParent.SelectedRowList = tempList;
}
else if (MegaParent.SelectedRowList.Contains(this) && (bool)GetValue(IsSelectedProperty) == false)
{
tempList.Remove(this);
MegaParent.SelectedRowList = tempList;
}
}
public Child(string name, string number, ViewSheet sheet, string paperSize, bool isSizeCompatible, PrintSheets_ViewModel megaParent)
{
Name = name;
Number = number;
Sheet = sheet;
PaperSize = paperSize;
IsSizeCompatible = isSizeCompatible;
MegaParent = megaParent;
}
}
This is the XAML code for the Treeview:
<Border Grid.Row="1" Grid.RowSpan="4" Grid.Column="3" Grid.ColumnSpan="3" Margin="10,10,10,10"
BorderBrush="Black" BorderThickness="1">
<ScrollViewer x:Name="treeScroller" PreviewMouseWheel="treeScroller_PreviewMouseWheel" VerticalScrollBarVisibility="Auto">
<TreeView Background="LightGray" ItemsSource="{Binding tv_model.ParentItems}" x:Name="sheetTree"
ScrollViewer.CanContentScroll="False">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type m:Parent}" ItemsSource="{Binding SheetList}">
<Border BorderBrush="DarkGray" CornerRadius="5" Padding="5,0,0,0" Height="40"
Width="{Binding Path=ActualWidth, ElementName=sheetTree, Converter={StaticResource minusFortyFiveConverter}}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="True">
<Setter Property="Background" Value="#b5ffc0"/>
<Setter Property="BorderThickness" Value="3"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="False">
<Setter Property="Background" Value="LightCyan"/>
<Setter Property="BorderThickness" Value="2"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"
IsChecked="{Binding Path=(h:CheckBox_Helper.IsChecked), Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Cursor="Hand"/>
<Label Grid.Column="1" Content="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Left" FontWeight="SemiBold"/>
<Label Grid.Column="3" VerticalAlignment="Center" HorizontalContentAlignment="Right"
FontWeight="Normal" FontSize="12"
Content="Sheets Checked:"/>
<Label Grid.Column="4" VerticalAlignment="Center" HorizontalAlignment="Right"
Content="{Binding CheckedRatio}">
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="14"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="False">
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="FontSize" Value="12"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Grid>
</Border>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type m:ChildList}">
<DataGrid x:Name="ChildDataGrid" ItemsSource="{Binding SheetChildList}"
Width="{Binding Path=ActualWidth, ElementName=sheetTree, Converter={StaticResource minusEightyConverter}}"
IsReadOnly="True" AutoGenerateColumns="False" SelectionMode="Extended" CanUserResizeColumns="True"
EnableRowVirtualization="True" CanUserAddRows="False" CanUserDeleteRows="False"
CanUserResizeRows="True" CanUserReorderColumns="False" CanUserSortColumns="True" >
<DataGrid.Resources>
<Style TargetType="DataGrid">
<EventSetter Event="LostFocus" Handler="OnLostFocus_DataGrid"/>
</Style>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSizeCompatible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSizeCompatible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Width="20">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Cursor="Hand" IsChecked="{Binding Path=(h:CheckBox_Helper.IsChecked), Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Sheet Number" Binding="{Binding Number}" IsReadOnly="True" Width="Auto"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" Width="*"/>
<DataGridTextColumn Header="Paper Size" Binding="{Binding PaperSize}" IsReadOnly="True" Width="Auto"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Margin" Value="0,3,0,3"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</ScrollViewer>
</Border>
I had the checkboxes working before I added some of the extra methods to the CheckBox_Helper, but I need those methods in order for other controls on my UI to function properly.
I feel like it could be a DataContext issue. I've tried adding RelativeSource to the IsChecked binding with the AncestorType as Datagrid, and I've also tried it as Window.
Still no luck, any help would be greatly appreciated.
So this one I am curious on as I may have to change my code base if I cannot get the data right. I was hoping a binding expert on WPF has had something similar and knew how to do it. I was following this guide, http://wpfthoughts.blogspot.com/2015/04/cannot-find-governing-frameworkelement.html, for binding a value in a list that is shown in datagrid to a combobox. Works great, if your property in the collection of objects is a primitive type. If it is complex not so much. I also want it to update the property when it changes implementing INotifyPropertyChanged.
Feel free to download the source code for easier reference: https://github.com/djangojazz/ComboBoxInDataGridViewWPF
BaseViewModel(just for INotifyPropertyChanged reuse):
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Essentially I have models as such:
public class Type
{
public Type(int typeId, string typeName)
{
TypeId = typeId;
TypeName = typeName;
}
public int TypeId { get; set; }
public string TypeName { get; set; }
}
public class TransactionSimple : BaseViewModel
{
public TransactionSimple(int transactionId, string description, int typeId, decimal amount)
{
TransactionId = transactionId;
Description = description;
TypeId = typeId;
Amount = amount;
}
public int TransactionId { get; set; }
public string Description { get; set; }
private int _typeId;
public int TypeId
{
get { return _typeId; }
set
{
_typeId = value;
OnPropertyChanged(nameof(TypeId));
}
}
public decimal Amount { get; set; }
}
public class TransactionComplex : BaseViewModel
{
public TransactionComplex(int transactionId, string description, int typeId, string typeName, decimal amount)
{
TransactionId = transactionId;
Description = description;
Type = new Type(typeId, typeName);
Amount = amount;
}
public int TransactionId { get; set; }
public string Description { get; set; }
private Type _type;
public Type Type
{
get { return _type; }
set
{
if(_type != null) { MessageBox.Show($"Change to {value.TypeName}"); }
_type = value;
OnPropertyChanged(nameof(Type));
}
}
public decimal Amount { get; set; }
}
And the ViewModel:
public sealed class MainWindowViewModel : BaseViewModel
{
private ObservableCollection<TransactionSimple> _simples;
private ObservableCollection<TransactionComplex> _complexes;
public MainWindowViewModel()
{
FakeRepo();
}
private ReadOnlyCollection<Type> _types;
public ReadOnlyCollection<Type> Types
{
get => (_types != null) ? _types : _types = new ReadOnlyCollection<Type>(new List<Type> { new Type(1, "Credit"), new Type(2, "Debit") });
}
public ObservableCollection<TransactionSimple> Simples
{
get { return _simples; }
set
{
_simples = value;
OnPropertyChanged(nameof(Simples));
}
}
public ObservableCollection<TransactionComplex> Complexes
{
get { return _complexes; }
set
{
_complexes = value;
OnPropertyChanged(nameof(Complexes));
}
}
private void FakeRepo()
{
var data = new List<TransactionComplex>
{
new TransactionComplex(1, "Got some money", 1, "Credit", 1000m),
new TransactionComplex(2, "spent some money", 2, "Debit", 100m),
new TransactionComplex(3, "spent some more money", 2, "Debit", 300m)
};
Complexes = new ObservableCollection<TransactionComplex>(data);
Simples = new ObservableCollection<TransactionSimple>(data.Select(x => new TransactionSimple(x.TransactionId, x.Description, x.Type.TypeId, x.Amount)));
}
}
UPDATED 2:24 PM PST USA: And finally the view(almost working):
<Window x:Class="ComboBoxInDataGridViewWPF.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:ComboBoxInDataGridViewWPF"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource x:Key="Types" Source="{Binding Types}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="SimpleExample" />
<DataGrid Grid.Row="1" ItemsSource="{Binding Simples}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="TransactionId" Binding="{Binding TransactionId}" />
<DataGridTextColumn Header="Description" Binding="{Binding Description}" />
<DataGridComboBoxColumn Header="Type" ItemsSource="{Binding Source={StaticResource Types}}" DisplayMemberPath="TypeName" SelectedValuePath="TypeId" SelectedValueBinding="{Binding Path=TypeId}" />
<DataGridTextColumn Header="Amount" Binding="{Binding Amount}" />
</DataGrid.Columns>
</DataGrid>
<Border Grid.Row="2" Height="50" Background="Black" />
<Label Content="ComplexObjectExample" Grid.Row="3" />
<DataGrid Grid.Row="4" ItemsSource="{Binding Complexes}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="TransactionId" Binding="{Binding TransactionId}" />
<DataGridTextColumn Header="Description" Binding="{Binding Description}" />
<!--This one works for the displays but not for the updates
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource Types}}" DisplayMemberPath="TypeName" SelectedItem="{Binding Type, Mode=TwoWay}" SelectedValue="{Binding Type.TypeId}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type.TypeName}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This one works but the initial displays are wrong. This seems to be the closest to what I want-->
<DataGridComboBoxColumn Header="Type" SelectedItemBinding="{Binding Type}" >
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Types}"/>
<Setter Property="DisplayMemberPath" Value="TypeName" />
<Setter Property="SelectedItem" Value="{Binding Type}" />
<Setter Property="IsReadOnly" Value="True"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Types}"/>
<Setter Property="DisplayMemberPath" Value="TypeName" />
<Setter Property="SelectedItem" Value="{Binding Type}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<!--This one does not work at all
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Types,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
DisplayMemberPath="TypeName" SelectedItem="{Binding Type}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<DataGridTextColumn Header="Amount" Binding="{Binding Amount}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
The problem is shown like this:
I can obviously get the items bound to the ComboBox and I have seen by adding Observable Collections(not shown) and raising properties that the complex type is getting called. But it will not display no matter what I try. Trying the property of the property like Type.TypeName or such with different combinations doesn't work. Any ideas?
This ridiculous behaviour is well known. Because DataGridColumn lies not in the visual tree, the classic way using the DataGridComboBoxColumn to bind the items from parent like you tried is not working.
Instead you could create DataGridTemplateColumn with a ComboBox inside. This should solve your problem nearly in the same way. If you want to bind the TypeId this code works:
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Types,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
DisplayMemberPath="TypeName"
SelectedValuePath="TypeId"
SelectedValue="{Binding Path=TypeId, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Binding the whole Type could be done by changing the ComboBox to:
<ComboBox ItemsSource="{Binding Path=DataContext.Types,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
DisplayMemberPath="TypeName"
SelectedItem="{Binding Path=Type, UpdateSourceTrigger=PropertyChanged}"/>
Alternatively you can have a look at this question where other possible solutions are described.
I'm creating a datagrid, with filters in the column headers. It works, but I don't think it's a good approach. Let me show you the code, very simple example:
The View
<Window x:Class="TestDataGridApp.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:TestDataGridApp.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<Window.DataContext>
<viewModels:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="DataGridHeader">
<DockPanel>
<TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<TextBox DockPanel.Dock="Top" Text="{Binding DataContext.FilterName, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
</DockPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding ItemCollection}" AutoGenerateColumns="False">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
ViewModel
namespace TestDataGridApp.ViewModels
{
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using TestDataGridApp.Entities;
using Prism.Mvvm;
public class MainWindowViewModel : BindableBase
{
private string _filterId;
private string _filterName;
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public MainWindowViewModel()
{
for (int i = 1; i <= 100; ++i)
{
Items.Add(new Item() {Id = i, Name = $"Item{i}"});
}
}
public string FilterId
{
get { return _filterId; }
set
{
SetProperty(ref _filterId, value);
TriggerFilters();
}
}
public string FilterName
{
get { return _filterName; }
set
{
SetProperty(ref _filterName, value);
TriggerFilters();
}
}
public ObservableCollection<Item> Items
{
get { return _items; }
set { SetProperty(ref _items, value); }
}
public ICollectionView ItemCollection => CollectionViewSource.GetDefaultView(Items);
private void TriggerFilters()
{
ItemCollection.Filter = o => FilterItem((Item)o);
}
private bool FilterItem(Item item)
{
try
{
bool checkId = false;
bool checkName = false;
int itemId = 0;
if (!string.IsNullOrEmpty(FilterId) && int.TryParse(FilterId, out itemId)) checkId = true;
if (!string.IsNullOrEmpty(FilterName)) checkName = true;
if (!checkId && !checkName) return true;
if (item == null) return false;
bool checkIdIsOk = (checkId && item.Id == int.Parse(FilterId) || !checkId);
bool checkNameIsOk = (checkName && item.Name.ToUpper().Contains(FilterName.ToUpper()) || !checkName);
if (checkIdIsOk && checkNameIsOk) return true;
}
catch (Exception e)
{
Console.WriteLine(e);
}
return false;
}
}
}
The Item
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
Basically simple datagrid, 2 columns. In each column there is a TextBox with binded filter. Each filter has its own field, so after the focus is lost, I can filter the grid by all filters.
My issue is.. I have a lot of columns. This is customized datagrid, so you can add and remove columns on the fly and there's a lot of duplicated code. Basically this is duplicated:
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<TextBox DockPanel.Dock="Top"
Text="{Binding DataContext.FilterId, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
</DockPanel>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
... only this <TextBox DockPanel.Dock="Top" Text="{Binding DataContext.FilterId, ... is changing for different columns.
So, I thought, I can easily replace it with this solution, but now.. I lost binding to my filter fields in the ViewModel:
<Window.Resources>
<DataTemplate x:Key="DataGridHeader">
<DockPanel>
<TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<TextBox DockPanel.Dock="Top" Text="{Binding DataContext.FilterName, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
</DockPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding ItemCollection}" AutoGenerateColumns="False">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
SOO.. I was thinking, to create a Dictionary for filters, where key would be the name of the column and in value I will store current filter (or null, if there's no filter at the moment for this column). Something like..
<TextBox x:Name="Foo" DockPanel.Dock="Top" Text="{Binding DataContext.FiltersDictionary[Foo], RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
But then I have to Biding contexts.. for one TextBox. I'm really not sure about this solution..
My question will be, how to create a parameter for DataTemplate in the above scenario?
Thanks for help!
PS. It's not a duplicate. This question is about "how to create a parameter for DataTemplate". The "duplicated" question is about dictionary as a binding - a potential solution for this question.. although probably NOT. As another user suggested there might be totally different, better solution to solve this problem. Two different things. I'm shocked that I have to explain this
Easiest way is to not rely only on xaml and add some code to help. For example use Loaded event of your TextBox like this:
<DataTemplate x:Key="DataGridHeader">
<DockPanel>
<TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<TextBox DockPanel.Dock="Top" Loaded="OnFilterBoxLoaded" />
</DockPanel>
</DataTemplate>
And setup binding when it is loaded:
private void OnFilterBoxLoaded(object sender, RoutedEventArgs e) {
var tb = (TextBox)sender;
// find column
DataGridColumnHeader parent = null;
DependencyObject current = tb;
do {
current = VisualTreeHelper.GetParent(current);
parent = current as DataGridColumnHeader;
}
while (parent == null);
// setup binding
var binding = new Binding();
// use parent column header as name of the filter property
binding.Path = new PropertyPath("DataContext.Filter" + parent.Column.Header);
binding.Source = this;
binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
tb.SetBinding(TextBox.TextProperty, binding);
}
You can use attached property to achieve the same, but I don't think it's needed in this case.
I used Evk solution with DependencyProperty instead of using Header
<controls:FilterDataGridTextColumn FilterName="Name" Header="Name" Binding="{Binding Path=Name}" Width="200" HeaderTemplate="{StaticResource HeaderTemplate}" />
FilterDataGridTextColumn :
public class FilterDataGridTextColumn : DataGridTextColumn
{
public static readonly DependencyProperty FilterNameProperty =
DependencyProperty.Register("FilterName", typeof(string), typeof(FilterDataGridTextColumn));
public string FilterName
{
get { return (string) GetValue(FilterNameProperty); }
set { SetValue(FilterNameProperty, value); }
}
}
I overrided DataGrid witn my class KDataGrid (needed some functionality for our purposes).
public class KDataGrid : System.Windows.Controls.DataGrid { *** }
It can be used with any collection with any RecordType (generic type).
Example XAML with simple KDataGrid:
<components:KDataGrid
CanUserAddRows="False"
IsReadOnly="True"
ItemsSource="{Binding Path=SomeCollection}">
<DataGrid.Columns>
<DataGridTextColumn
Header="ID"
Binding="{Binding Id}"
Width="50"/>
<DataGridTextColumn
Header="Code"
Binding="{Binding Code}"
Width="130"/>
</DataGrid.Columns>
</components:KDataGrid>
ViewModel contains:
public class SomeRecord: BaseRecord
{
public long ID { get; set; }
public string Code { get; set; }
}
public List<SomeRecord> SomeCollection { get; }
KDataGrid (DataGrid) is readonly. Because of that standart empty row in bottom of DataGrid not shows.
If collection SomeCollection is empty, it's not possible to set focus on DataGrid (for add new record for example).
Easiest way to fix that (as I think) is: display empty row (which is focusable).
But if collection contains at least one row thats not need to display empty row.
Is there any ideas how can I do that ?
Is there any ideas how can I do that ?
If you want to display an empty row in the DataGrid you could add an "empty" 'SomeRecord' object to your 'SomeCollection' in your view model:
SomeCollection.Add(new SomeRecord());
The other option is to use a Style that sets the ItemsSource property of the DataGrid to a CompositeCollection that contains an empty item only when the SomeCollection source property is empty:
<DataGrid CanUserAddRows="False" IsReadOnly="True" AutoGenerateColumns="False">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="ItemsSource" Value="{Binding SomeCollection}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SomeCollection.Count}" Value="0">
<Setter Property="ItemsSource">
<Setter.Value>
<CompositeCollection>
<local:SomeRecord />
</CompositeCollection>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" Width="50"/>
<DataGridTextColumn Header="Code" Binding="{Binding Code}" Width="130"/>
</DataGrid.Columns>
</DataGrid>
In both cases you probably want to change the type of the ID property from long to long? in order for the ID column to be blank instead of displaying 0 which is the default value for a long.
Problem solution is simple.
Thiere is no need to add empty record.
It just need to get focus by clicking on ScrollViewer:
public class KDataGrid : System.Windows.Controls.DataGrid
{
public KDataGrid()
{
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var scroll = FindVisualChild<ScrollViewer>();
if (scroll != null)
{
scroll.MouseDown += (o, args) =>
{
if (IsKeyboardFocusWithin)
return;
Focus();
};
}
}
public static T FindVisualChild<T>(this DependencyObject root) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(root, i);
if (child != null && child is T)
return (T)child;
else
{
T childOfChild = child.FindVisualChild<T>();
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
}
I am trying to change the color of my data grid row when the checkbox of the given row is checked, and when unchecked it should reset the value to the previous one.
I am using MVVM to achieve the above mentioned functionality.
My XAML CODE :-
<Window.Resources>
<Style x:Key="RowStyle" TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsChecked, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<DataGrid Name="lbUsers" ItemsSource="{Binding Data}" CanUserAddRows="False" Grid.Column="1" Grid.Row="1" SelectedIndex="{Binding SelectionIndexChange, Mode=TwoWay}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Width="45" Height="20" Command="{Binding ElementName=lbUsers,Path=DataContext.IsChecked}" ></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
Given below is view model code :
public ViewModel ()
{
Data = new ObservableCollection<CommonData>
{
};
}
private ObservableCollection<CommonData> _data;
public ObservableCollection<CommonData> Data
{
get
{
if (_data == null)
{
_data = new ObservableCollection<CommonData>()
{
};
}
return _data;
}
set
{
if (value != this._data)
{
this._data = value;
NotifyPropertyChanged("Data");
}
}
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set { this._isChecked = value; NotifyPropertyChanged("IsChecked"); }
}
Please let me know what wrong am i doing to get the given functionality working.
Thanks in advance, in case of missing information please let me know.
A couple things:
You've assigned an x:Key to the style, but are not using it on the DataGrid. Remove the key to make it the default style for all DataGridRow, or add this to the grid:
RowStyle="{StaticResource RowStyle}"
Within the DataTrigger binding, you will also need add
ElementName=lbUsers
Additionally, your Checkbox is not bound properly -- this is not done through a Command. You will need to change
Command={Binding...
To
IsChecked={Binding...