Convert <DataGridTemplateColumn> properties to <DataGridTestColumn> - c#

In my application I was using this code for my datagrid text boxes:
<DataGridTemplateColumn Header="EECAT From"
Width="Auto"
IsReadOnly="False">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding EECAT_From, Mode=TwoWay}"
HorizontalAlignment="Center"
Margin="0,2,0,2"
Background="Transparent" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I recently learned about two way databinding using Linq. My original text column code would not work with two way data binding and I had to changed the text box's to code like this:
<DataGridTextColumn Binding="{Binding EECAT_From, Mode=TwoWay}"
Header="EECAT From"
IsReadOnly="False"
x:Name="txtEECAT_From" >
</DataGridTextColumn>
My two way binding now works but I am having a problem setting all the formatting that I originally had into the new . Is it possible to set up the missing formatting options or is it possible to use two way data binding with my original text column code?

Related

How can I compose the static text "new Operation Class“ with the databound objects listed in datagrid?

I have a datagrid in my WPF application.
This datagrid is bound to an ObservableCollection which is called OperationClasses as defined below.
The type OperationClass consists of 3 properties which are Name, Start and End respectively.
I would like to know how to add OperationClass as a datagrid row to the DataGridList by clicking into the last row with the text "new Operation Class“ as shown in the image as an attached file.
DataGrid picture with example values:
After the end user has entered a name and a start and end value then the text "new Operation Class" will be displayed in the next line, which is the new last line.
How can I compose the static text "new Operation Class" with the databound objects listed in DataGrid?
public ObservableCollection<OperationClass> OperationClasses
{
get
{
return _operationClasses;
}
set
{
SetProperty(ref _operationClasses, value);
}
}
I tried to find a way to use CompositeCollection so as to compose databound objects(OperationClass) and the static text "New Operation Class". But i couldnt succeed in for the current stage..
First off all, I would like to thank you for valuable post.
But I have some issues regarding add new item operation. I defined the issues by sharing the corresponding images as shown below.
After user has entered a name and a start and end value then the text "new Operation Class“ should be displayed in the next line, which is the new last line. In other words, the properties which are Name,Start and End are compulsory columns to be filled for the placeholder to be displayed in the next line.
In addition to this, even though I can select the new Item place holder row as a selected row, I cant do it for data bound records. I will be very delighted if you can have the opportunity to inform me.
Before adding new Item
After adding new item
Selected New Item Row
Selected Data Bound Item Row
To make it easier, I am sharing the xaml code as shown below.
<UserControl.Resources>
<ResourceDictionary>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding DataContext.SelectedTreeItem.Name, Mode=OneWay}"></Setter>
</Style>
<Style TargetType="DataGridCell">
<Setter Property="BorderBrush" Value="Gainsboro"></Setter>
<Setter Property="BorderThickness" Value="0"></Setter>
</Style>
<Style TargetType="DataGridRow">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="{StaticResource ContentBackgroundColorBrush}"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="White"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="DarkBlue"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
<Grid>
<DataGrid x:Name="OperationClassDataGrid"
ItemsSource="{Binding PlantMachine.OperationClasses, UpdateSourceTrigger=PropertyChanged}"
AlternationCount="2"
CanUserAddRows="True"
CanUserDeleteRows="True"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
AutoGenerateColumns="False"
IsReadOnly="False">
<DataGrid.Columns>
<DataGridTextColumn Visibility="Hidden" Header="{DynamicResource Plant.OperationClass.List.Header.Key}" Binding="{Binding OperationKey}" Width="Auto" SortDirection="Ascending"/>
<DataGridTemplateColumn Header="{DynamicResource Plant.OperationClass.List.Header.Name}">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type model:OperationClass}">
<TextBlock Text="{Binding OperationName}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<!-- Instead of defining the CellTemplate explicitly,
we assign the CellTemplateSelector instead -->
<DataGridTemplateColumn.CellTemplateSelector>
<local:OperationClassTemplateSelector>
<local:OperationClassTemplateSelector.DefaultTemplate>
<DataTemplate DataType="{x:Type model:OperationClass}">
<TextBox Text="{Binding OperationName}" />
</DataTemplate>
</local:OperationClassTemplateSelector.DefaultTemplate>
<local:OperationClassTemplateSelector.PlaceholderTemplate>
<DataTemplate>
<TextBlock Text="new operation class..." />
</DataTemplate>
</local:OperationClassTemplateSelector.PlaceholderTemplate>
</local:OperationClassTemplateSelector>
</DataGridTemplateColumn.CellTemplateSelector>
</DataGridTemplateColumn>
<DataGridTextColumn Header="{DynamicResource Plant.OperationClass.List.Header.Start}" Binding="{Binding RangeStartValue}" Width="Auto" SortDirection="Ascending"/>
<DataGridTextColumn Header="{DynamicResource Plant.OperationClass.List.Header.End}" Binding="{Binding RangeEndValue}" Width="Auto" SortDirection="Ascending"/>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="/Resources/Images/Delete.png" Width="125" Height="30" HorizontalAlignment="Right">
<Image.InputBindings>
<MouseBinding Gesture="LeftClick" CommandParameter="{Binding Path=Key}" Command="{Binding DataContext.RemoveOperationClassCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
</Image.InputBindings>
</Image>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</AdornerDecorator>
</UserControl>
You need to provide a DataTemplateSelector to apply a dedicated DataTemplate to the placeholder column.
First of all, the placeholder item (or placeholder table row) must be enabled by setting DataGrid.CanUserAddRows to true. The value is true by default so just make sure you don't explicitly set it to false.
The following example assumes that you want the placeholder text to fill the first column. However, you can follow the below pattern to add a placeholder text for any column.
Following your code, the example assumes that the data model used to populate the DataGrid is the OperationClass with two fictional properties to make two columns:
OperationClass.cs
class OperationClass : INotifyPropertyChanegd
{
public string TextDataFirstColumn { get; set; }
public string TextDataSecondColumn { get; set; }
}
First, define the DataTemplateSelector:
OperationClassTemplateSelector.cs
class OperationClassTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate PlaceholderTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
=> item switch
{
OperationClass _ => this.DefaultTemplate,
var dataItem when dataItem is not null => this.PlaceholderTemplate,
_ => base.SelectTemplate(item, container),
};
}
Second step is to explicitly define column templates.
It is important to define the column that should contain the placeholder text as DataGridTemplateColumn. This is required in order to allow the assignment of a DataTemplateSelector implementation.
The first column of the placeholder row will display "Add new item..." as placeholder text:
<DataGrid ItemsSource="{Binding OperationClassItems}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="1st Column">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type local:OperationClass}">
<TextBox Text="{Binding TextDataFirstColumn}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<!-- Instead of defining the CellTemplate explicitly,
we assign the CellTemplateSelector instead -->
<DataGridTemplateColumn.CellTemplateSelector>
<local:OperationClassTemplateSelector>
<local:OperationClassTemplateSelector.DefaultTemplate>
<DataTemplate DataType="{x:Type local:OperationClass}">
<TextBlock Text="{Binding TextDataFirstColumn}" />
</DataTemplate>
</local:OperationClassTemplateSelector.DefaultTemplate>
<local:OperationClassTemplateSelector.PlaceholderTemplate>
<DataTemplate>
<TextBlock Text="Add new item..." />
</DataTemplate>
</local:OperationClassTemplateSelector.PlaceholderTemplate>
</local:OperationClassTemplateSelector>
</DataGridTemplateColumn.CellTemplateSelector>
</DataGridTemplateColumn>
<DataGridTextColumn Header="2nd Column"
Binding="{Binding TextDataSecondColumn}" />
</DataGrid.Columns>
</DataGrid>

Unable to bind DataTemplateColumn element to another DataTemplateColumn

I am trying to set an element in each DataGrid row by setting another element in the same row.
If IsChecked of the ToggleButton is True, image in the ContentControl will become visible.
I have cannibalised this example to try to get this to work. This example seems similiar too and I hope I am not duplicating anything.
This is the code snippet from my implementation of the DataGrid:
<Grid>
<StackPanel>
<Grid Margin="0" Grid.Column="0" Grid.Row="3">
<DataGrid
ItemsSource="{Binding Path=. , Mode=OneWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False"
Height="Auto"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
x:Name="Filter_grid"
Grid.Row="1">
<DataGrid.Columns >
<DataGridTextColumn Header="CAN ID" Binding="{Binding Information.CAN_ID}" Width="50" />
<DataGridTextColumn Header="Messagen Name" Binding="{Binding Information.CAN_ID_description}" Width="300" />
<DataGridTextColumn Binding="{Binding Information.Status}" Width="50" />
<DataGridTemplateColumn Header = "Filter ON" Width="SizeToCells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding Information.Tick}">
<ContentControl.Style>
<Style TargetType = "ContentControl" >
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding = "{Binding Path=IsChecked, ElementName=Filter_on}" Value="True">
<Setter Property = "Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="F_column" Header ="Select">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton x:Name="Filter_on" Content="Switch" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</StackPanel>
</Grid>
I am unable to get the ElementName to find the ToggleBox and get Cannot find source for binding with reference 'ElementName=Filter_on'.
I have tried doing similiar with
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridTemplateColumn}}, Path=F_column.Filter_on}" Value="True">, or using x:Reference which throws me an exception I can't decipher.
First things first: you should never do this. XAML is very flexible and allows you to do some terribly clever things, but just because you can doesn't mean you should. The correct solution in this case is to create a view model for each row element with a boolean property that both the button and your data trigger can bind to. Apart from being more flexible it's also much easier to test, debug and log etc.
That said, what you are asking is technically possible. DataGrids are actually quite complex due to various optimizations they employ etc, so you still need to use an intermediate property, but since you don't want to use a view model you'll have to use the Tag property in the button's DataGridCell instead (which can be used for arbitrary user data):
Bind the button's IsChecked property to do a one-way-to-source binding to it's parent DataGridCell's Tag property.
Bind the DataTrigger to find the parent DataGridCellsPanel, and then bind directly to the appropriate child's Tag property i.e. Children[1].Tag.
Put it together and you get this:
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header = "Filter ON" Width="SizeToCells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType = "ContentControl" >
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding = "{Binding Path=Children[1].Tag, RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}" Value="True">
<Setter Property = "Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
<TextBlock Text="Content goes here" />
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="F_column" Header ="Select">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton x:Name="Filter_on" Content="Switch" IsChecked="{Binding Path=Tag, RelativeSource={RelativeSource AncestorType=DataGridCell}, Mode=OneWayToSource}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
But seriously...just use a view model.

WPF: Best way to react to change in datagrid content?

I have a datagrid with two readonly DataGridTextColumns and one DataGridTemplateColumn with a TextBox in it. (The reason I went for a textbox rather than just a text column is because i wanted a context menu on each datagrid row with an option to rename which which turned out to be easiest with this approach).
<DataGrid Grid.Row="0" Name="dgUsers" AutoGenerateColumns="False" ItemsSource="{Binding Path=Users, Mode=TwoWay}">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource dataGridRowContextMenu}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" />
<DataGridTemplateColumn Width="Auto" Header="Middle Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Name="middleName" Text="{Binding MiddleName}" Style="{StaticResource DataGridTextBox}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
The contents of the datagrid are twoway binded to a collection of objects in my view model. Each row corresponds to a User. Those Users objects in my viewmodel are hooked up to a database. I want to make things such that when a user changes the text in the editable column (the column where each row has a text box), the database is modified accordingly (after some data validation and such).
My question is: what is a good MVVM way of listening for a change in the template column and reacting to it?
I tried using the TextChanged event:
<DataGridTemplateColumn Width="Auto" Header="Middle Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Name="middleName" Text="{Binding MiddleName}" Style="{StaticResource DataGridTextBox}"
TextChanged="middleName_TextChanged"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
But then the middleName_TextChanged() method in my xaml.cs code behind gets called with every character added to the text box... ideally I want a method that only gets called when the focus leaves the text box (not with every character added).
I also considered launching the method to do input validation from the set method of the Property of User that this column is binded to (the set method of User.MiddleName) but this too is called with each character inserted into the text box.
In your .xaml file add a CollectionViewSource like this
<Window.Resources>
<CollectionViewSource x:Key="ItemCollectionViewSource" CollectionViewType="ListCollectionView"/>
</Window.Resources>
And then in your DataGrid put the DataContext to ItemCollectionViewSource like this
DataContext="{StaticResource ItemCollectionViewSource}"
Now in code behind set your database to the ItemCollectionViewSource like this
CollectionViewSource itemCollectionViewSource;
itemCollectionViewSource = (CollectionViewSource)(FindResource("ItemCollectionViewSource"));
itemCollectionViewSource.Source = YourDataBase;
Now any changes in a twoway binding would reflect on the database.

C# WPF DataGrid change cell icon resource programmatically

I've never used a DataGrid in C# before (and done little C#) and I'm struggling to change a DataGrid cell icon I've set in XAML. By default its set to a Dynamic resource of appbar_question and upon certain conditions being met I'd like to be able to change it to a resource of appbar_check (I'm using the mahapps icons).
XAML code
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False" ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn HeaderStyle="{StaticResource CenterMe}" Header="Website" Binding="{Binding Website}">
<DataGridTextColumn.CellStyle>
<Style>
<Setter Property="FrameworkElement.HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTemplateColumn HeaderStyle="{StaticResource CenterMe}" Header="Status" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Click="ShowStatus">
<Button.Template>
<ControlTemplate>
<Rectangle Width="16" Height="16" Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}">
<Rectangle.OpacityMask>
<VisualBrush Stretch="Fill" Visual="{DynamicResource appbar_question}" />
</Rectangle.OpacityMask>
</Rectangle>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
First I tried to access the cell directly but I see DataGrids in WPF like you to bind to an object(s) then access them as items and change properties in that object. However I tried doing that but couldn't figure out how to bind to a resource in my object?
Try use DataTemplate.Triggers:
1. Name your VisualBrush:
<VisualBrush x:Name="myBrush"...
Add DataTemplate.Triggers:
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding to_my_property}" Value="my_condition">
<Setter TargetName="myBrush" Property="Visual" Value="{DynamicResource appbar_check}"/>
</DataTrigger>
</DataTemplate.Triggers>
Hope it helps.

NewLine in centered DataGridTextColumn Header with Resources File

I would like to achieve that the Header in a DataGridColumn has a newline (2 lines) and each text is centered. Furthermore the text should come from a resource file.
I've tried to make a newline in the resource file with ALT+ENTER but the Text is not centered and looks like this:
| LINE1LONG |
| LINE2 |
My DataGridTextColumn in WPF
<DataGridTextColumn Width="4*" IsReadOnly="False"
Header="{x:Static p:Resources.DG_HEADER_DESC}" Binding="{Binding Description, Mode=TwoWay}">
</DataGridTextColumn>
The centering works for a single line but not with two lines with the following style:
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center"></Setter>
</Style>
Does anyone have an idea?
you can create a custom template for the header where you center the text within a textblock and not the textblock within the header:
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn>
<DataGridTextColumn.Header>
<GridViewColumnHeader>
<TextBlock TextAlignment="Center" Text="this is a text
and here some more" />
</GridViewColumnHeader>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
If you want to maintain the ability to use a resource file
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn>
<!-- Column Width--!>
<DataGridTextColumn.Width>4</DataGridTextColumn.Width>
<!-- Column Width--!>
<DataGridTextColumn.IsReadOnly>False</DataGridTextColumn.IsReadOnly>
<DataGridTextColumn.Binding>
<Binding Path="{Binding Description, Mode=TwoWay}"/>
</DataGridTextColumn.Binding>
<DataGridTextColumn.Header>
<DataGridColumnHeader Content="{x:Static p:Resources.DG_HEADER_DESC}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
TextBlock.TextAlignment="Center">
<!-- Header Width--!>
<DataGridColumnHeader.Width>4</DataGridColumnHeader.Width>
<!-- Header Width--!>
</DataGridColumnHeader>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>

Categories

Resources