Unable to bind DataTemplateColumn element to another DataTemplateColumn - c#

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.

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>

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.

WPF DataGridColumn conditional control

I am currently displaying a hyperlink in my data grid with the following DataGridHyperLinkColumn definition:
<DataGridHyperlinkColumn Header="Item" Binding="{Binding Item, Mode=OneWay}">
<DataGridHyperlinkColumn.ElementStyle>
<Style>
<EventSetter Event="Hyperlink.Click" Handler="ButtonItemInfo_OnClick"/>
</Style>
</DataGridHyperlinkColumn.ElementStyle>
</DataGridHyperlinkColumn>
I want to change this to conditionally display a hyperlink or a label (or textblock). So if the bound value is "SH", I want to display the label. Otherwise I want the hyperlink.
How can I accomplish this?
I would bind the Hyperlink to a command and conditionally return false in CanExecute. You can style the hyperlink for disabled state.
<DataGrid ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Item">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink Command="{Binding DataContext.Navigate, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding}" />
</Hyperlink>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And the ViewModel:
public ObservableCollection<string> Items
{
get;
set;
}
public ICommand Navigate
{
get
{
return new RelayCommand(
(param) => DoNavigate(param as string), // execute
(param) => // can execute
{
var link = param as string;
return link != "SH";
});
}
}
If you really need a textbox for some rows you can use a DataTrigger in the columns cell style.
<DataGrid ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Item">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Template">
<Setter.Value>
<!-- Template for normal rows -->
<ControlTemplate>
<TextBlock>
<Hyperlink Command="{Binding DataContext.Navigate,
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding}" />
</Hyperlink>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="SH">
<Setter Property="Template">
<Setter.Value>
<!-- Template for SH rows -->
<ControlTemplate>
<TextBlock Text="{Binding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

Convert <DataGridTemplateColumn> properties to <DataGridTestColumn>

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?

DataGrid's CellEditingTemplate and focus in edit mode

I am having an issue with WPFToolkit DataGrid when a column is customized supplying both CellTemplate and CellEditingTemplate. If you take a look below, you will see my editing template has a single CheckBox. All is fine in a functional sense but when F2 is hit to edit the cell, one must also hit TAB in order for the CheckBox to receive focus. Ideally, one would hit F2 and SPACE to toggle the value. Currently, one must hit F2, TAB, SPACE. I have tried setting TabIndex to no avail. I am running out of ideas.
<WPFToolkit:DataGridTemplateColumn Header="Turn"
MinWidth="60">
<WPFToolkit:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Height="16">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding CanTurn}" Value="True">
<Setter Property="Source" Value="/Images/16/Tick.png" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</DataTemplate>
</WPFToolkit:DataGridTemplateColumn.CellTemplate>
<WPFToolkit:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=CanTurn}" HorizontalAlignment="Center" HorizontalContentAlignment="Center" />
</DataTemplate>
</WPFToolkit:DataGridTemplateColumn.CellEditingTemplate>
</WPFToolkit:DataGridTemplateColumn>
Try this
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<CheckBox Name="checkbox" IsChecked="{Binding Path=CanTurn}" HorizontalAlignment="Center" HorizontalContentAlignment="Center" />
<DataTemplate.Triggers>
<Trigger SourceName="checkbox" Property="IsVisible" Value="True">
<Setter TargetName="checkbox" Property="FocusManager.FocusedElement" Value="{Binding ElementName=checkbox}" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
Or this...
<DataGridTemplateColumn Header="Long" IsReadOnly="False" Width="100">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<CheckBox FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}" IsChecked="{Binding Path=CanTurn}" HorizontalAlignment="Center" HorizontalContentAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
if you want to set the focus on edit and select the text given by a Binding try this.
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Parameter0, Mode=TwoWay}" Loaded="TbLoaded" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
code behind:
private void TbLoaded(object sender, EventArgs e)
{
TextBox tb = sender as TextBox;
if (tb == null) return;
tb.SelectAll();
FocusManager.SetFocusedElement(this, tb);
}

Categories

Resources