WPF DataGrid Custom Row Header - c#

I am working on a DB FrontEnd with WPF / EntityFramework / MVVM
Now i got stuck when i allow the user to add data to a datagrid (which is bound to an Observable Collection).
What i want to achieve, is to get a row header like in MS Access:
So my WPF DataGrid should look like this basically:
Is there any way to bind the RowHeaderStyle to the RowState?
For Example:
RowState.Editing: Show Edit Icon
RowState.NewRow: Show Star
RowState.Default: Show Default Row Header
I found no solution so far, but i think WPF should pe powerfull enough to get this job done.
Thank you!

Simple. Give the DataGrid a RowHeaderStyle that swaps in different ContentTemplates depending on the state of the DataGridRow. Fortunately the DataGridRow is a visual ancestor of the DataGridRowHeader, so it's relatively simple to reach up there with a RelativeSource binding and get the values of the relevant properties: DataGridRow.IsEditing and DataGridRow.IsNewItem.
I used <Label>New</Label> etc. as an arbitrary stand-in for whatever content you want to use.
<DataGrid
ItemsSource="{Binding Rows}"
>
<DataGrid.RowHeaderStyle>
<Style
TargetType="{x:Type DataGridRowHeader}"
BasedOn="{StaticResource {x:Type DataGridRowHeader}}"
>
<!--
Empty content template for default state.
Triggers below replace this for IsNewItem or IsEditing.
-->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label></Label>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger
Binding="{Binding
IsEditing,
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"
Value="True"
>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label>Edit</Label>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger
Binding="{Binding
IsNewItem,
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"
Value="True"
>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label>New</Label>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowHeaderStyle>
</DataGrid>

Related

WPF: Show Tooltip message on custom DataGrid cells when IDataErrorInfo

I'm pretty new to WPF but I've read a lot on it as well as MVVM in the last couple if days.
My WPF displays a DataGrid with custom column templates (using the NumericUpDown Controls from Xceed WPF Toolkit). Three of the columns contain the decimal coordinates of a 3D vector. I use IDataErrorInfo to make sure the vector's length is never 0 (all three columns cannot be 0 at the same time). This is working fine so far, the cells are marked red when the validation fails, but I also want to show the error message in a tooltip or similar.
<DataGrid [...]>
<DataGrid.Columns>
[...]
<DataGridTemplateColumn Header="X" [...]>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<xctk:DecimalUpDown Value="{Binding PositionX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}">
</xctk:DecimalUpDown>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
[... and same thing for Y and Z]
</DataGrid.Columns>
</DataGrid>
This is where I'm stuck for a few hours now, so I hope you can help me here:
How can I show a error tooltip on custom template columns?
I've read through a lot of articles and threads on error tooltips but most of them are on plain TextBox or DataGridTextColumns and a tried a lot but could not make it working so far.
Most of them look something like this:
<Style x:Key="errorStyle" TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="-2"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
from here:
https://msdn.microsoft.com/library/ee622975%28v=vs.100%29.aspx
or more exsamples:
Display validation error in DataGridCell tooltip
https://stackoverflow.com/a/7900510/5025424
https://harishasanblog.blogspot.de/2011/01/datagrid-validation-using.html
WPF data validation and display error message IDataErrorInfo and error templates
Nothing on this ever showed any Tooltip to me.
Can you give me a hint,
how this style trigger definition has to look for cells containing no TextBox,
where the definition has to be
and if the column needs the reference this definition in some way?
Thank you!
Set the Style property of the control to a Style with a trigger that sets the Tooltip property of the control in the CellTemplate if the attached Validation.HasError property returns true:
<DataGridTemplateColumn Header="X">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<xctk:DecimalUpDown Value="{Binding PositionX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}">
<xctk:DecimalUpDown.Style>
<Style TargetType="xctk:DecimalUpDown">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Self}}" />
</Trigger>
</Style.Triggers>
</Style>
</xctk:DecimalUpDown.Style>
</xctk:DecimalUpDown>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Dynamic content of a Grid

My data model has a property of the enumeration type. I wonder if there is way to place dynamically a user control based on the value of the enumeration type?
I am currently investigating in the following direction:
<Grid Name ="AdjustmentsArea" DockPanel.Dock ="Right" MinWidth ="100" Visibility ="Collapsed" >
<ContentControl DataContext ="{Binding AjustmentView}">
<Style TargetType ="model:AjustmentViews">
<Style.Triggers>
<DataTrigger Binding ="{Binding}" Value ="Settings">
/// is it possible in principle to point a user control using a Setter ???
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl>
</Grid>
May be also I am on a wrong path. But I would like to know (learn) if it is possible to implement this requirement for dynamic content in user control, but not using hide/show exised element approach.
What would you recommend?
you can set different template depending on trigger binding value
<ContentControl DataContext ="{Binding AjustmentView}">
<ContentControl.Style>
<Style TargetType ="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value ="Settings">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate> <!--template with UserControl here--> </ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
e.g. WPF Slider uses this approach when Orientation changes (Horizontal or Vertical)

Textblock won't recieve trigger in HierachicalDataTemplate

I'm trying to get a TreeView to display items as a TextBlock, and then based on a boolean inside the data-bound object to either make the FontWeight Normal or Bold, pretty much the following:
<TreeView x:Name="TreeView" ItemsSource="{Binding Layers}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewModels:Layer}" ItemsSource="{Binding Path=Layers}">
<TextBlock Text="{Binding Path=Name}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowInPreview}">
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
The Setter outside the trigger actually works, when I set that one to "Bold", everything goes Bold right away. It's just the DataTrigger that never, well... triggers :P
The ItemSource implements INotifyPropertyChanged, and so does the Layer object on all properties (including the ShowInPreview).
I've tried all kinds of different setups I could find on the web (using Window.Resources, putting it in TreeView.ItemContainerStyle, etc. etc), so I'm completely at a loss right now!
Set the Value on your data trigger.
I dont know exactly where is your property, try something like this. I think, issue in binding:
<DataTrigger Binding="{Binding Path=DataContext.ShowInPreview, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}}">

Passing View as Command Parameter in WPF.Does it violet MVVM approach?

I have data grid and there some number of columns in it.There are number of rows. I want to show one window when user clicks on the context menu of that row.I need first columns value in viewmodel from that row for some logic.Currently I am passing placement target as command parameter i.e.gridviewrow. Following is my code
<telerik:RadGridView.RowStyle>
<Style TargetType="telerik:GridViewRow">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" ></Setter>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="show Window" Command="{Binding PlacementTarget.Tag.DataContext.ShowChart,RelativeSource={RelativeSource AncestorType=ContextMenu, Mode=FindAncestor}}" CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"></MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</telerik:RadGridView.RowStyle>
How can I pass value of first column of particular row on which user has clicked?
Does it violet MVVM approach?What is solution if it violets MVVM approach in this case?
Teoretically yes, because the only way to access the view is by INotifyPropertyChanged and IErrorDataInfo. However, it depends what do you inttend to do. If you want to change the visibilty of a UI elemnt, I violet the MVVM pattern because the other way arround it seems to complicated to me. I suggest to tell what exactly do you want to do, and maybe I will be able to help you :)
Maybe You can try to create a new ViewModel class for the grid's rows and each column is a property of the ViewModel. So you can easily bind the data context of the row to that ViewModel and the columns to its properties.
I will post another answer since you edited masively the question.
If you use a datagrid in your view, the your view (XAML code) should look like this:
<Window x:Class="CareerTrackWpfClient.Views.User_Main_Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="User_Main_Window" Background="Black">
<Window>
<Grid HorizontalAlignment="Center" Visibility="Collapsed" Name="gridbooks">
<DataGrid Height="650" HorizontalAlignment="Stretch" Name="BooksGrid" RowHeight="90" ColumnWidth="200"
ColumnHeaderHeight="40" HeadersVisibility="Column" Background="Transparent" RowBackground="DarkGray"
AlternatingRowBackground="LightBlue" BorderBrush="Gray"
BorderThickness="2" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Book #" Width="220" Binding="{Binding BookID}" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Book Title" Width="220" Binding="{Binding Title}" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
You should have a model like this
public class Book
{
public int BookID {get; set;}
public string Title {get;set;}
}
Now, either you do a ViewModel where you fill the list (like Zarzan said) - and this way you will respect the MVVM pattern. Or in the code beheind, in the constructor, bellow the InitializeComponent() method, you write something like this
List<Book> booksProvider=new List<Book>();
booksProvider.Add(new Book{BookID=1,"Book 1"}) ;
booksProvider.Add(new Book{BookID=2,"Book 2"}) ;
gridbooks.ItemsSource=booksProvider;
Is very nice using this UI component (DataGrid), from my point of view. It has the validations on place. Try to enter strings on the BookID field, and it will notify you. It is very flexible and you don't have to install anything.
Hope it helps as many developers as possible.

Datagrid and Listview with same itemssource

I have a WPF application with a DataGrid and ListView that share the same ObservableCollection ItemsSource. When the DataGrid's CanUserAddRows property is True it causes the ListView to display the extra item that the DataGrid uses to add new rows.
How can I get the extra row from the DataGrid to not show in the ListView?
I tried using a trigger on the ListView's DataTemplate and checking if the items Id was empty or 0
`<ListView.ItemTemplate>
<DataTemplate>
<Label Margin="-2,0,0,0" Name="CategoryLabel" >
<TextBlock TextWrapping="Wrap" Text="{Binding categoryName}" Height="46"></TextBlock>
</Label>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding categoryId}" Value="0" > <!-- also tried Value="" -->
<Setter TargetName="CategoryLabel" Property="Visibility" Value="Hidden" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>`
I just posted an answer to a problem of changing the template using a data template selector
Change View with its ViewModel based on a ViewModel Property
Possibly just because I have recently looked at this but I wonder if it might be possible to use the same technique here.
Have one template for where the category has a value,then another blank template for values without a category. The important part is you do the test in code rather than XAML so easier to inspect.
You can solve your problem without any modification of your ViewModel or code behind. You can do well without explicitly defining CollectionView's of any kind. Just add to your view's XAML one more (or only) DataTrigger that triggers on the NewItemPlaceholder item of the default view of ListView ItemsSource's collection. Have this trigger to set the UIElement.Visibility attached property to "Hidden". Place it within ItemContainerStyle style triggers. Like this:
<ListView
ItemsSource="{Binding ...}"
>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
...
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding}"
Value="{x:Static CollectionView.NewItemPlaceholder}">
<Setter Property="UIElement.Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="..." Value="{Binding ...}" />
...
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label Margin="..." Name="...">
<TextBlock TextWrapping="Wrap"
Text="{Binding ...}" />
</Label>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>

Categories