Binding Enum with entity not working - wpf

I have a table Called IGdaily with Field Trans_Category. I want to bind a DataGridComboBoxColumn displaying enum and binds its int value to datagrid cell Trans_Category.
My Enum
public enum Enm_Purch_Ret : short
{
Purchase = 1,
Sale = 2,
Return = 3
}
Viewmodel Vm_Purchase
public class Vm_Purchase : INotifyPropertyChanged
{
private IGoldEntities db = new IGoldEntities();
public ObservableCollection<IGdaily> Vm_IGdaily { get; set; }
public ObservableCollection<Enm_Purch_Ret> Vm_Enum_P_R { get; set; }
public Vm_Purchase()
{
Vm_IGdaily = new ObservableCollection<IGdaily>();
Vm_Enum_P_R = new ObservableCollection<Enm_Purch_Ret>(Enum.GetValues(typeof(Enm_Purch_Ret)).Cast<Enm_Purch_Ret>().ToList());
}
public ObservableCollection<IGdaily> IGDailys
{
get { return Vm_IGdaily; }
set { Vm_IGdaily = value; NotifyPropertyChanged(); }
}
public ObservableCollection<Enm_Purch_Ret> Enm_Purch_Rets
{
get { return Vm_Enum_P_R; }
set { Vm_Enum_P_R = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In XAML
<Window.DataContext>
<local:Vm_Purchase/>
</Window.DataContext>
<DataGrid x:Name="DG" ItemsSource="{Binding IGDailys}" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="Cell" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding GroupName}" Header="Name" Width="200"/>
<DataGridComboBoxColumn Header="Item/Metal" SelectedValueBinding="{Binding Trans_Category}" SelectedValuePath="{Binding Path=Enm_Purch_Rets, StringFormat='\{0:D\}'}" DisplayMemberPath="Enm_Purch_Ret">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Enm_Purch_Rets, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Enm_Purch_Rets , RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridTextColumn Binding="{Binding Trans_Category}" ClipboardContentBinding="{x:Null}" FontSize="14" Header="Metal Id" Width="100"/>
</DataGrid.Columns>
</DataGrid>
public partial class IGdaily
{
public int GDaily_Id { get; set; }
public int DailyMast_Id { get; set; }
public int ItemGroup_Id { get; set; }
public int Item_Id { get; set; }
public int Trans_Category { get; set; }
}
Please help what is my mistake in binding. I am new to MVVM pattern.
please explain
Thanks

You can't set an int property to a Enm_Purch_Ret value because there is no implicit conversion between these two types.
But you could use a converter class that peforms the conversion for you:
public class EnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Enm_Purch_Ret e = (Enm_Purch_Ret)value;
return (int)e;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int i = (int)value;
return (Enm_Purch_Ret)i;
}
}
Usage:
<DataGrid x:Name="DG" ItemsSource="{Binding IGDailys}" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="Cell" >
<DataGrid.Resources>
<local:EnumConverter x:Key="conv" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding GroupName}" Header="Name" Width="200"/>
<DataGridComboBoxColumn Header="Item/Metal"
SelectedItemBinding="{Binding Trans_Category, Converter={StaticResource conv}}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Enm_Purch_Rets, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Enm_Purch_Rets , RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridTextColumn Binding="{Binding Trans_Category}" ClipboardContentBinding="{x:Null}" FontSize="14" Header="Metal Id" Width="100"/>
</DataGrid.Columns>
</DataGrid>

Related

How to use GridViewComboBoxColumn and allow the user to edit?

I need to present a WPF GridView where one column is a Combobox, The user can select one value from the list or enter a new value so I set the IsComboBoxEditable to true but the problem is that if the user types a value that is not in the ItemsSource the Text is blank when the Combobox looses the focus.
Note : I don't want, when a new value is typed , this value to be
added to the ItemsSource. I only need to save it's string value in row
that bounded to it.
I also need DropDownOpened event, to populate it's ItemsSource.
Here is my code:
<telerik:GridViewDataColumn Header="Description">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<telerik:RadComboBox IsEditable="True" ItemsSource="{Binding Descriptions}" Text="{Binding Description1,Mode=TwoWay}" DropDownOpened="descriptionRadComboBox_DropDownOpened"/>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
</telerik:GridViewDataColumn>
Description1 is string property, and Descriptions is List of string that populate in runtime.(When DropDownOpened Event occurred)
Like you mentioned, your goal is, simply, to "Editable ComboBox".
(And, of course, you don't want to add new Item to ItemsSource)
<telerik:GridViewDataColumn UniqueName="description1" Header="Description">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description1}"></TextBlock>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
<telerik:GridViewDataColumn.CellEditTemplate>
<DataTemplate>
<telerik:RadComboBox Name="SLStandardDescriptionsRadComboBox" IsEditable="True"
ItemsSource="{Binding DataContext.SLStandardDescriptions, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
DisplayMemberPath="SLStandardDescriptionTitle" DropDownOpened="Description_DropDownOpened">
</telerik:RadComboBox>
</DataTemplate>
</telerik:GridViewDataColumn.CellEditTemplate>
</telerik:GridViewDataColumn>
Codebehinde :
private void RadGridView_CellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
{
if (e.Cell.Column.UniqueName == "description1")
{
RadComboBox combo = e.Cell.ChildrenOfType<RadComboBox>().FirstOrDefault();
if (combo != null)
{
List<Description> comboItems = combo.ItemsSource as List<Description>;
string textEntered = e.Cell.ChildrenOfType<RadComboBox>().First().Text;
bool result = comboItems.Contains(comboItems.Where(x => x.DescriptionTitle == textEntered).FirstOrDefault());
if (!result)
{
comboItems.Add(new Description { DescriptionTitle = textEntered });
combo.SelectedItem = new Description { DescriptionTitle = textEntered };
}
if (_viewModel.AccDocumentItem != null)
{
if (e.Cell.Column.UniqueName == "description1")
_viewModel.AccDocumentItem.Description1 = textEntered;
}
}
}
}
Here is the solution for .net DataGrid control:
<DataGrid ItemsSource="{Binding Path=Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Title}" ></DataGridTextColumn>
<DataGridComboBoxColumn SelectedValueBinding="{Binding ComboItem.ID}" DisplayMemberPath="ComboTitle" SelectedValuePath="ID">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.ComboItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.ComboItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<Setter Property="IsEditable" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
Surely you can do this using Telerik DataGrid control as well.
And here is my ViewModel:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
ComboItems = new ObservableCollection<ComboItem>()
{
new ComboItem(){ID=1,ComboTitle="ComboItem1"},
new ComboItem(){ID=2,ComboTitle="ComboItem2"},
new ComboItem(){ID=3,ComboTitle="ComboItem3"}
};
Items = new ObservableCollection<Item>()
{
new Item(){ID=1,Title="Item1",ComboItem=ComboItems[0]},
new Item(){ID=2,Title="Item2",ComboItem=ComboItems[1]},
new Item(){ID=3,Title="Item3",ComboItem=ComboItems[2]}
};
}
public ObservableCollection<Item> Items { get; set; }
public ObservableCollection<ComboItem> ComboItems { get; set; }
}
public class Item
{
public int ID { get; set; }
public string Title { get; set; }
public ComboItem ComboItem { get; set; }
}
public class ComboItem
{
public int ID { get; set; }
public string ComboTitle { get; set; }
}

WPF commands CanExecute validation inside a template

I have a nested datagrid where I have + and - buttons that are bound to RelayCommands that add a new row or delete the current one respectively. The minus button command's CanExecute logic is supposed to disable the current row's minus button if only one item is left in its category.
The problem is that it disables all minus buttons in all categories because of its template nature.
Image
How can this be mitigated?
Here's the code.
XAML
<Grid>
<DataGrid x:Name="dataGrid1"
ItemsSource="{Binding DataCollection}"
SelectedItem="{Binding dataCollectionSelectedItem, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="false" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Item/Price" Width="*">
<DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<DataGrid x:Name="dataGridItem"
ItemsSource="{Binding Items}"
SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.itemsSelectedItem, Mode=TwoWay}"
Background="Transparent"
HeadersVisibility="None"
AutoGenerateColumns="False"
CanUserAddRows="false" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Width="*"/>
<DataGridTextColumn Binding="{Binding Price}" Width="50"/>
<DataGridTemplateColumn Header="Button">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.AddItem }" Width="20" Height="20">+</Button>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteItem }" Width="20" Height="20">-</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Category" Binding="{Binding Category}" Width="Auto"/>
<DataGridTemplateColumn Header="Buttons">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.AddCategory}" Width="20" Height="20">+</Button>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteCategory}" Width="20" Height="20">-</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
C#
public class Item
{
public string Name { get; set; }
public int Price { get; set; }
}
public class DataTable
{
public ObservableCollection<Item> Items { get; set; }
public string Category { get; set; }
}
public class RelayCommand : ICommand
{
private Action<object> executeDelegate;
readonly Predicate<object> canExecuteDelegate;
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new NullReferenceException("execute");
executeDelegate = execute;
canExecuteDelegate = canExecute;
}
public RelayCommand(Action<object> execute) : this(execute, null) { }
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return canExecuteDelegate == null ? true : canExecuteDelegate(parameter);
}
public void Execute(object parameter)
{
executeDelegate.Invoke(parameter);
}
}
public class ViewModel
{
public ObservableCollection<DataTable> DataCollection { get; set; }
public DataTable dataCollectionSelectedItem { get; set; }
public Item itemsSelectedItem { get; set; }
public RelayCommand DeleteCategory { get; private set; }
public RelayCommand AddCategory { get; private set; }
public RelayCommand DeleteItem { get; private set; }
public RelayCommand AddItem { get; private set; }
public ViewModel()
{
DataCollection = new ObservableCollection<DataTable>
{
new DataTable() {
Items = new ObservableCollection<Item> {
new Item { Name = "Phone", Price = 220 },
new Item { Name = "Tablet", Price = 350 },
},
Category = "Electronic gadgets" },
new DataTable() {
Items = new ObservableCollection<Item> {
new Item { Name = "Teddy Bear Deluxe", Price = 2200 },
new Item { Name = "Pokemon", Price = 100 },
},
Category = "Toys" }
};
DeleteItem = new RelayCommand(innerDeleteItem, canUseDeleteItem);
AddItem = new RelayCommand(innerAddItem, canUseAddItem);
}
public void innerDeleteItem(object parameter)
{
var collectionIndex = DataCollection.IndexOf(dataCollectionSelectedItem);
if (DataCollection[collectionIndex].Items.Count != 1)
{
DataCollection[collectionIndex].Items.Remove(itemsSelectedItem);
CollectionViewSource.GetDefaultView(DataCollection).Refresh();
}
}
public bool canUseDeleteItem(object parameter)
{
var collectionIndex = DataCollection.IndexOf(dataCollectionSelectedItem);
if ((dataCollectionSelectedItem != null) && (DataCollection[collectionIndex].Items.Count == 1))
{
return false;
}
else return true;
}
public void innerAddItem(object parameter)
{
var collectionIndex = DataCollection.IndexOf(dataCollectionSelectedItem);
var itemIndex = DataCollection[collectionIndex].Items.IndexOf(itemsSelectedItem);
Item newItem = new Item() { Name = "Item_Name", Price = 0 };
DataCollection[collectionIndex].Items.Insert(itemIndex + 1, newItem);
CollectionViewSource.GetDefaultView(DataCollection).Refresh();
}
public bool canUseAddItem(object parameter)
{
return true;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel newViewModel = new ViewModel();
this.DataContext = newViewModel;
}
}
You're binding your two Commands to Window's Data Context, and it should bind to DataGrid's Data Context.
Change your xaml to:
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.AddItem }" Width="20" Height="20">+</Button>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.DeleteItem }" Width="20" Height="20">-</Button>
</StackPanel>
I have eventually set the button's CanExecute to always return true and styled the button with a custom trigger that disables it when Items.Count turns 1. Perhaps there are more elegant solutions but at least this one works for me.
<Button Content="-"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteItem }"
Width="20" Height="20">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=Items.Count }" Value="1">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

How to change WPF listview row background colour dynamically?

I am new to WPF, I have the following xaml code for list view:
<ListView x:Name="listView1" ItemsSource="{Binding Processes1}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="470" Margin="10,95,150,6" Width="565" SelectionChanged="NetscalerCfgView_listView1_SelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn Header="Line" DisplayMemberBinding="{Binding srcCfgLineNum}"/>
<GridViewColumn Header="Source Config" DisplayMemberBinding="{Binding srcConfigText}"/>
</GridView>
</ListView.View>
</ListView>
I have the class SrcListViewInfo which I am displaying in listview:
public class SrcListViewInfo
{
public int srcCfgLineNum { get; set; }
public string srcConfigText { get; set; }
}
I have declared it in windows load event like this:
public ObservableCollection<SrcListViewInfo> processes1 = null;
processes1 = new ObservableCollection<SrcListViewInfo>();
I want to color the row background dynamically in a different function under different cases dynamically, for example:
case DiffResultSpanStatus.DeleteSource:
for (i = 0; i < drs.Length; i++)
{
SrcListViewInfo newInfo = new SrcListViewInfo();
newInfo.BackgroundColor = new SolidColorBrush(Colors.Red);
// newInfo.BackgroundColor = Brushes.Red;
newInfo.srcCfgLineNum = cnt;
newInfo.srcConfigText = ((TextLine)source.GetByIndex(drs.SourceIndex + i)).Line;
// newInfo.BackgroundColor = Brushes.Red; << want to set the color like this.
I have tried solid brush but it does not seem to be working correctly.
You can Style the ListViewItem in xaml directly,
Example:
Assuming your "Name" variable is a string, you can try
<ListView Name="whatever">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Name}"
Value="John">
<Setter Property="Background"
Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
....
Now any ListView Row with "Name" Value of "John" will have a "Red" Background
an option
is to use IMultiValueConverter in ListView.ItemTemplate
<ListView DataContext="{Binding}" ItemsSource="{Binding Models}" AlternationCount="3" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name }"/>
<TextBlock Text="{Binding Desc }"/>
<StackPanel.Background>
<MultiBinding Converter="{StaticResource BackConverter}">
<Binding />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}"/>
</MultiBinding>
</StackPanel.Background>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class BackConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// write your logic (You have the model and the view model)
var index = ((ItemsControl)values[1]).Items.IndexOf(values[0]);
if (index % 2 == 0)
return Brushes.Gray;
return Brushes.White;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
another option
is to use ItemsControl.AlternationIndex in ListView.ItemContainerStyle
<ListView DataContext="{Binding}" ItemsSource="{Binding Models}" AlternationCount="3" >
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="Red" />
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="Green" />
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="2">
<Setter Property="Background" Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Edit
public MainWindow()
{
InitializeComponent();
lv.ItemsSource = new List<string> { "a", "b", "c", "d", "e" };
}
After some googling i found out my own solution I am using Listview.ItemsSource and as source i use List with ListViewItems Then i can set background of specify ListViewItem in List, and just refresh listview.
XAML:
<ListView x:Name="listView" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" Grid.Row="1">
<ListView.View>
<GridView>
<GridViewColumn Header="IP" DisplayMemberBinding="{Binding IP}" Width="Auto"/>
<GridViewColumn Header="PING" DisplayMemberBinding="{Binding Ping}" Width="Auto"/>
<GridViewColumn Header="Host Name" DisplayMemberBinding="{Binding DNS}" Width="Auto"/>
<GridViewColumn Header="Mac" DisplayMemberBinding="{Binding MAC}" Width="Auto"/>
<GridViewColumn Header="Výrobce" DisplayMemberBinding="{Binding Manufacturer}" Width="Auto"/>
</GridView>
</ListView.View>
</ListView>
Fill ListView with Items with Gray Background:
List<ListViewItem> ITEMS = new List<ListViewItem>();
private void button_Click(object sender, RoutedEventArgs e)
{
for (int i = 1; i < 20; i++)
{
ListViewItem OneItem = new ListViewItem();
OneItem.Background = Brushes.LightGray;
OneItem.Content = new Device() { IP = "1.1.1.1", Ping = "30ms", DNS = "XYZ", MAC = "2F:3C:5F:41:F9", Manufacturer = "Intel" };
ITEMS.Add(OneItem);
listView.ItemsSource = ITEMS;
}
listView.Items.Refresh();
}
public class Device
{
public string IP { get; set; }
public string Ping { get; set; }
public string DNS { get; set; }
public string MAC { get; set; }
public string Manufacturer { get; set; }
}
Create Method for Row Change Color:
private void ChangeRowColor(int RowIndex,SolidColorBrush NewBackground)
{
ITEMS[RowIndex].Background = NewBackground;
listView.Items.Refresh();
}
And use it:
private void button1_Click(object sender, RoutedEventArgs e)
{
ChangeRowColor(4, Brushes.Green);
}

Binding to attached property not working

I am trying to prepare template for DataGridColumnHeader. Template should be really simple.
Attached dependency property csdpp:CalendarProperties.EnumDay should only specific day that DataGridColumnHeader belong to and converter should then just return right label for that day.
If I set AncestorType to DataGridTextColumn (that is what I want) and leave the code like this:
<Style TargetType="DataGridColumnHeader" x:Key="DayHeaderStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding
RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridTextColumn}, Mode=OneWay,
Path=(csdpp:CalendarProperties.EnumDay),
Converter={StaticResource IndexToDayLabelConverter}}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
nothing happen. Converter is not even getting called.
But if I change code to this:
<Style TargetType="DataGridColumnHeader" x:Key="DayHeaderStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding
RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Mode=OneWay,
Path=(csdpp:CalendarProperties.EnumDay),
Converter={StaticResource IndexToDayLabelConverter}}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
(DatagridTextColumn switched by DataGrid, DataGrid has attached property too (below))
Converter is getting called and as value is passed attached property from DataGrid. Why is that working for DataGrid and not for DataGridTextColumn? Please help.
Code with DataGrid and DataGridTextColumn:
<DataGrid Grid.Row="1" Grid.Column="1"
x:Name="_shiftDataGrid"
ItemsSource="{Binding ElementName=Root, Path=PersonShiftgroupings.ShiftPersons}"
DataContext="{Binding ElementName=Root, Path=PersonShiftgroupings.ShiftPersons}"
AutoGenerateColumns="False"
csdpp:CalendarProperties.EnumDay="Fri">
<DataGrid.Columns>
<DataGridTextColumn
csdpp:CalendarProperties.EnumDay="Wed"
HeaderStyle="{StaticResource DayHeaderStyle}">
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Any help would be appreciated.
Like i stated in my comment above DataGridColumnHeader is in the VisualTree of DataGrid via
DataGridColumnHeaderPresenter and not in the VisualTree of DataGridColumn.
You can reach the Column through DataGridColumnHeader's Column property
Column
I didn't want to get into your implementation and logic because i'm sure there's a cleaner way of doing what ever it is you need done.
Here's a sample of what you need to get it working :
CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
public List<SomeItem> MyItems
{
get { return new List<SomeItem> { new SomeItem() , new SomeItem() , new SomeItem() , new SomeItem() }; }
}
}
public class SomeItem
{
public int First { get { return 1; } }
public int Second { get { return 2; } }
public int Third { get { return 3; } }
public int Forth { get { return 4; } }
}
public static class ASample
{
public static string GetMyProperty(DependencyObject obj)
{
return (string)obj.GetValue(MyPropertyProperty);
}
public static void SetMyProperty(DependencyObject obj, string value)
{
obj.SetValue(MyPropertyProperty, value);
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.RegisterAttached("MyProperty", typeof(string), typeof(ASample));
}
public class ColumnHeaderConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return string.Empty;
DataGridColumn c = (DataGridColumn)value;
string header = ASample.GetMyProperty(c);
return header;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML :
<Window.Resources>
<local:ColumnHeaderConverter x:Key="colConverter"/>
<Style TargetType="DataGridColumnHeader" x:Key="DayHeaderStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding
RelativeSource={RelativeSource AncestorType=DataGridColumnHeader}, Mode=OneWay,
Path=Column , Converter={StaticResource colConverter} }"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<DataGrid AutoGenerateColumns="False" ColumnHeaderStyle="{StaticResource DayHeaderStyle}"
ItemsSource="{Binding MyItems}" local:ASample.MyProperty="DataGrid" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding First}" local:ASample.MyProperty="11"/>
<DataGridTextColumn Binding="{Binding Second}" local:ASample.MyProperty="22"/>
<DataGridTextColumn Binding="{Binding Third}" local:ASample.MyProperty="33"/>
<DataGridTextColumn Binding="{Binding Forth}" local:ASample.MyProperty="44"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
DataGridTextColumn is no ancestor of DataGridColumnHeader

WPF dataGrid (or ListView) filled via Binding, different Template for Rows

I have a WPF dataGrid which is filled via DataBinding. This list contains different columns. I have two types of rows, one type contains all the columns in the rows, and the other should span one column over all the columns.
Is there a easy way to make this possible? (maybe use a ListView instead of a DataGrid?)
I attached a screenshot how it should look like:
I now tried with Item Template Selector:
My templates in the Resources (The two templates are not correct, but they are only for testing!)
<DataTemplate x:Key="commentTemplate">
<TextBlock Text="{Binding}"/>
</DataTemplate>
<DataTemplate x:Key="normalTemplate">
<Image Source="{Binding }" />
</DataTemplate>
<WPFVarTab:VarTabRowItemTemplateSelector
NormalRowsTemplate="{StaticResource normalTemplate}"
CommentRowsTemplate="{StaticResource commentTemplate}"
x:Key="vartabrowItemTemplateSelector" />
and my Datagrid:
<DataGrid AutoGenerateColumns="False" Margin="0,22,0,22"
Name="dataGrid" Grid.RowSpan="2" CanUserAddRows="True"
RowBackground="Azure" AlternatingRowBackground="LightSteelBlue"
ItemTemplateSelector="{StaticResource vartabrowItemTemplateSelector}" >
and my Template Selector:
public class VarTabRowItemTemplateSelector : DataTemplateSelector
{
public DataTemplate NormalRowsTemplate { get; set; }
public DataTemplate CommentRowsTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
S7VATRow vRow = item as S7VATRow;
if (vRow == null || string.IsNullOrEmpty(vRow.Comment))
return NormalRowsTemplate;
return CommentRowsTemplate;
}
}
I put a stop in the first row in SelectTemplate but this is never called!
You can use item template selector with ListView. Or with DataGrid, it's available there, too. Here is an example.
Use binding to turn the visibility of datarow on and off like so. I am assuming you defined columns.
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource CommentTemplate}" Content="{Binding Comment}" Visibility="{Binding IsCommentVisible, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
Check this
http://www.wpf-tutorial.com/datagrid-control/details-row/. Incase the link is broken, code for row and column details for your reference is pasted below.
<Window x:Class="WpfTutorialSamples.DataGrid_control.DataGridDetailsSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataGridDetailsSample" Height="200" Width="400">
<Grid Margin="10">
<DataGrid Name="dgUsers" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Birthday" Binding="{Binding Birthday}" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Details}" Margin="10" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
</Window>
Code view model
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples.DataGrid_control
{
public partial class DataGridDetailsSample : Window
{
public DataGridDetailsSample()
{
InitializeComponent();
List<User> users = new List<User>();
users.Add(new User() { Id = 1, Name = "John Doe", Birthday = new DateTime(1971, 7, 23) });
users.Add(new User() { Id = 2, Name = "Jane Doe", Birthday = new DateTime(1974, 1, 17) });
users.Add(new User() { Id = 3, Name = "Sammy Doe", Birthday = new DateTime(1991, 9, 2) });
dgUsers.ItemsSource = users;
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
public string Details
{
get
{
return String.Format("{0} was born on {1} and this is a long description of the person.", this.Name, this.Birthday.ToLongDateString());
}
}
}
}
viewmodel for data and comment. Also for empty space.
public abstract class RowViewModelBase
{
public bool IsSpecial { get; set; }
}
public class EmptySpaceViewModel : RowViewModelBase
{
public EmptySpaceViewModel() => IsSpecial = true;
}
public class DataViewModel : RowViewModelBase
{
public DataViewModel(string column1, string column2)
{
Column1 = column1;
Column2 = column2;
}
public string Column1 { get; set; }
public string Column2 { get; set; }
}
public class CommentViewModel : RowViewModelBase
{
public string Comment { get; }
public CommentViewModel(string comment)
{
Comment = comment;
IsSpecial = true;
}
}
template selector
public class RowContentSelector : DataTemplateSelector
{
public DataTemplate EmptyTemplate { get; set; }
public DataTemplate CommentTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is EmptySpaceViewModel)
return EmptyTemplate;
if (item is CommentViewModel)
return CommentTemplate;
return base.SelectTemplate(item, container);
}
}
xaml
<Window
...>
<Window.Resources>
<DataTemplate x:Key="EmptyRow">
<Border Height="30" />
</DataTemplate>
<DataTemplate x:Key="CommentTempate">
<TextBlock Foreground="Green" Text="{Binding Comment}" />
</DataTemplate>
<local:RowContentSelector
x:Key="RowContentSelector"
CommentTemplate="{StaticResource CommentTempate}"
EmptyTemplate="{StaticResource EmptyRow}" />
</Window.Resources>
<DataGrid
AutoGenerateColumns="False"
CanUserAddRows="False"
ItemsSource="{Binding}"
RowDetailsTemplateSelector="{StaticResource RowContentSelector}"
RowDetailsVisibilityMode="Visible">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Column1}" Header="column1" />
<DataGridTextColumn Binding="{Binding Column2}" Header="column2" />
</DataGrid.Columns>
<DataGrid.ItemContainerStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRow}">
<Border
x:Name="DGR_Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<SelectiveScrollingGrid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGridCellsPresenter
x:Name="cells"
Grid.Column="1"
ItemsPanel="{TemplateBinding ItemsPanel}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<DataGridDetailsPresenter
x:Name="details"
Grid.Row="1"
Grid.Column="1"
SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=AreRowDetailsFrozen, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}}"
Visibility="{TemplateBinding DetailsVisibility}" />
<DataGridRowHeader
x:Name="header"
Grid.RowSpan="2"
SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.Row}}" />
</SelectiveScrollingGrid>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsSpecial}" Value="True">
<Setter TargetName="cells" Property="Visibility" Value="Collapsed" />
<!--<Setter TargetName="header" Property="Visibility" Value="Collapsed" />-->
</DataTrigger>
<DataTrigger Binding="{Binding IsSpecial}" Value="False">
<Setter TargetName="details" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ItemContainerStyle>
</DataGrid>
</Window>
example
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new List<RowViewModelBase> {
new EmptySpaceViewModel(),
new DataViewModel("aaaa", "bbb"),
new EmptySpaceViewModel(),
new DataViewModel("aaaa", "bbb"),
new CommentViewModel("//comment"),
new DataViewModel("aaaa", "bbb"),
new CommentViewModel("//comment"),
new CommentViewModel("//comment"),
new DataViewModel("aaaa", "bbb"),
};
}
}
Of course, if you has one alternative template - you no need template selector and can set template directly
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Comment}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>

Resources