Dynamically changing WPF ListView ItemsPanel & ItemsContainerStyle - wpf

I have a ListView in XAML which displays its source collection in a GridView (with columns) fashion.
However I intend to use the same ListView to display the source collection may be in a grid of images, or some card view.
I want the ListView to change itself based on a ComboBox selection. So say for ComboBox value 1 ListView should display a GridView, for value 2 ListView should display card view.
Currently my ListView specifies a GridView set as its View property:
<ListView ItemsSource="{Binding PersonList}" Width="450" HorizontalAlignment="Right" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn Header="FirstName" DisplayMemberBinding="{Binding FirstName}" />
<GridViewColumn Header="LastName" DisplayMemberBinding="{Binding LastName}" />
<GridViewColumn Header="Ip Address" DisplayMemberBinding="{Binding Path=IpAddress}" />
</GridView>
</ListView.View>
</ListView>
I would like to know how can I change the ListView to display different views based on ComboBox triggers.

ListView View is a DependencyProperty of type ViewBase. So, what you can do is create your own custom views and can set it via DataTrigger on combobox selected item.
Microsoft already has sample available for it online which you can download from here.
OR
May be you can define two separate ListView's in resources as two seperate DataTemplates.
<Window.Resources>
<DataTemplate x:Key="GridViewTemplate">
<ListView/> <!-- GridView -->
</DataTemplate>
<DataTemplate x:Key="CardViewTemplate">
<ListView/> <!-- CardView -->
</DataTemplate>
</Window.Resources>
and have one ContentControl in place and you can swap its Content based on selected value in combobox.
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content"
Value="{StaticResource GridViewTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedValue}" Value="Value2">
<Setter Property="Content"
Value="{StaticResource CardViewTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

Related

How to create ToolTip for DataGridColumnHeader in WPF?

I have found how to create ToolTip for DataGridColumnHeader in WPF (and make sure that the Header is wrapped).
<Style x:Key="StartingTabToolTipHeaderStyle" TargetType="DataGridColumnHeader">
<Setter Property="ToolTip" Value="{x:Static r:Resource.StartingTabToolTip}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="Wrap" Text="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
I can use this style in this way:
<DataGridComboBoxColumn Header="{x:Static r:Resource.StartingTab}" SelectedItemBinding="{Binding StartingTab}"
HeaderStyle="{StaticResource StartingTabToolTipHeaderStyle}">
This solution is not nice because I need to create a separate style for each header because the tooltip is hardwired in the style. I would like a general style which can be used in this way:
<DataGridComboBoxColumn Header="{x:Static r:Resource.StartingTab}" SelectedItemBinding="{Binding StartingTab}"
MyToolTip="{x:Static r:Resource.StartingTabToolTip}">
Any idea how to do it? May be with attached property?
You can set the ToolTipService.ToolTip attached property on each column and bind your values to it, because it is not used on columns.
<DataGridComboBoxColumn Header="{x:Static r:Resource.StartingTab}"
SelectedItemBinding="{Binding StartingTab}"
HeaderStyle="{StaticResource StartingTabToolTipHeaderStyle}"
ToolTipService.ToolTip="{x:Static r:Resource.StartingTabToolTip}">
Then you can bind this text on the column in your header style using a RelativeSource binding.
<Style x:Key="StartingTabToolTipHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ToolTip" Value="{Binding Column.(ToolTipService.ToolTip), RelativeSource={RelativeSource Self}}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="Wrap" Text="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Now you only have a single style. If you want to avoid setting the style on each column, make it an implicit style by omitting the x:Key. Then it will be applied to all matching types in scope.
<Style TargetType="{x:Type DataGridColumnHeader}">
You could use DataGridTemplateColumn
<DataGrid AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="yourHeader" HeaderStyle={StaticResource HeaderStyle1}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--you can add any control such as ComboBox, Button etc-->
<TextBlock Text="{Binding Message}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<!--Editing cell template is not mandatory, only if you want allow user to edit a particular cell, shown here only for reference-->
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<!--When the user double clicks on this cell, it changes the control to TextBox (from TextBlock - see above) to allow for user input-->
<TextBox Text="{Binding Message}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<!--Second Column-->
<DataGridTemplateColumn Header="secondHeader" HeaderStyle={StaticResource HeaderStyle2}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemSource="{Binding SomeSource}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
This might seem a bit much for each column. But it gives you far more control & better scalability for future complex changes. Its somewhat trivial work put in during the initial dev phase.
Atleast thats my opinion since after the success of the concept, several further inputs/changes & new requirements come in to improve user interactivity with specific rules & logic on how/when the user can interact with UI Elements especially when altering data.
Edit -
Your style can be exactly like you made i.e. targeting DataGridColumnHeader.

Why is my WPF ListView column not right aligning?

In my Explorer view that displays a list of files, I have tried right-aligning the Size column as follows. I have the following resources:
<UserControl.Resources>
<converters:FileSizeConverter x:Key="FileSizeConverter" />
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
<DataTemplate x:Key="SizeTemplate">
<TextBlock HorizontalAlignment="Right" Text="{Binding Size, Converter={StaticResource FileSizeConverter}}" />
</DataTemplate>
</UserControl.Resources>
FileSizeConverter is just an IValueConverter that converts numeric file sizes into string format with B, KB, MB etc. I then have the following column:
<GridViewColumn CellTemplate="{StaticResource SizeTemplate}" Header="Size" Width="80" />
This displays the file size correctly converted, e.g. 8,2 KB, but still left-aligned. I have followed the example in the Microsoft Docs article How to: Change the Horizontal Alignment of a Column in a ListView, so what could be wrong here?
There are several complications here.
If you run Snoop and take a look at what you end up with in your view you'll find the textblock is hosted in a gridviewrowpresenter.
This is applying a bunch of code to lay out it's contents.
If you really want to know exactly how that works then you could pick through the source code here https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/GridViewRowPresenter.cs
If you're more interested in how to get this to work as you want then there are two things to do:
1) Make the columns stretch:
<ListView.Resources>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.Resources>
2) Either put the textblock in a container that can fill that space and allow the textblock to align right. Or, more simply. Set TextAlignment on your textblock.
<DataTemplate x:Key="SizeTemplate">
<TextBlock TextAlignment="Right"
To right align the text in a ListView GridViewColumn:
Set the ListViewItem.HorizontalContentAlignment="Stretch" so the column cells inherit it
Set up a CellTemplate for the column you want to right-align with a TextBlock with its HorizontalAlignment="Right"
You can't use DataMemberBinding on the column, otherwise the CellTemplate will be ignored
For example:
<ListView Name="_priceListListView" ItemsSource="{Binding PriceList}" >
<!-- How to set ListViewItem.HorizontalContentAlignment="Stretch" -->
<ListView.Resources>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridView.Columns>
<!-- Left-aligned text column -->
<GridViewColumn Header="Item" DisplayMemberBinding="{Binding Path=Item}" />
<!-- Right-aligned numeric column -->
<GridViewColumn Header="Price" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Price, StringFormat={}{0:C}}" HorizontalAlignment="Right" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>

User Friendly Way to Display a ComboBox in a WPF DataGrid using MVVM

I have a WPF application using MVVV; I have a collection of items in an object, one of the properties of the items in the ItemsCollection is an enum, I want the user to be able to edit the properties of the items in the ItemsCollection using a drop down combo box or text field. The collection is displayed in a DataGrid. I am having extreme difficulty getting the combo box to display and allow the user to interact with it and the textbox in a very simple manner (single click, keyboard input). Right now it takes two clicks into a cell to be able to type in the 'name' field where I would want a single click to be able to allow the user to edit the value.
It also takes Three! clicks to expand open the contents of the combobox using the method I have below.
I've tried numerous ways found here on stack overflow but this was the first solution I have found that will even get the enum to populate the combobox and call the set in my property enum
Would someone be able to help me find a way that would allow the user to update entries with a single click on a given row (i.e to click into a text field for editing or click a ComboBox to expand it) I've been at this for several hours now and I feel like I'm understanding less and less about how this DataGrid works with focus and selected cells/rows. I've got the XAML below here - no code behind.
basically: DataGrid, user can click on a cell in the column to select all the text inside the cell for editing, or they can click a ComboBox to cause it to drop down with the enums so that they may select a value and apply it to the item with a further click
<Window.Resources>
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="GetEnumValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="MockItem+ValidItemsType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<DataGrid
Grid.Column="0"
Margin="0,0,0,5"
SelectionMode="Single"
SelectedItem="{Binding Model.SelectedMockItem, UpdateSourceTrigger=LostFocus}"
Name="ModelItemDataGrid"
MaxHeight="350"
VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Model.MockObject.MockItemsCollection}"
Height="Auto"
HorizontalGridLinesBrush="#CBCBCB"
VerticalGridLinesBrush="#CBCBCB"
AutoGenerateColumns="False"
IsReadOnly="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="Auto" />
<DataGridComboBoxColumn Width="120" ItemsSource="{Binding Source={StaticResource GetEnumValues}}" SelectedValueBinding="{Binding ValidItem}" />
<DataGridTemplateColumn x:Name="SelectedItemColumn" Header="Valid Item Type" Width="Auto" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox IsReadOnly="True" Text="{Binding Path=ValidItem}" d:DataContext="{d:DesignData MockItem}" >
<TextBox.Style>
<Style TargetType="{x:Type TextBox}" >
<Setter Property="Background" >
<Setter.Value>
<SolidColorBrush Color="{Binding Path=ValidItem, Converter= {StaticResource MockItemValidItemToBackgroundColourConverter1}}" d:DataContext="{d:DesignData MockItem}" />
</Setter.Value>
</Setter>
</Style>
</TextBox.Style>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Style TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsEditing" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
this will help you, I had the same problem with you.

WPF XAML - How to bind a DataTrigger to a ComboBox value

I currently have a ListView that takes in an item and displays a ComboBox and a Button.
I would like to dynamically show or hide the button based on the value of the ComboBox being equal to "BlahBlahBlah".
Currently the inside of my <GridView> looks like this:
<GridViewColumn Header="Property" Width="160">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="PropertyComboBox"
ItemsSource="{Binding Path=ArisingPropertyList, Mode=TwoWay}"
SelectedValue="{Binding ArisingProperty.PropertyName,
UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="PropertyName" Width="140" >
</ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="30" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="...">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedValue.PropertyName,
ElementName=PropertyComboBox}" Value="HideButton">
<Setter Property="Visibility" Value="BlahBlahBlah" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Can someone point out where I'm going wrong? All I'm getting is:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=PropertyComboBox'. BindingExpression:Path=SelectedValue.PropertyName; DataItem=null; target element is 'Button'; target property is 'NoTarget' (type 'Object')
You have already bounded SelectedValue property to your source class, so bind directly to that property instead of ComboBox. Issue is ComboBox and Button lies in different Visual trees so can't be bind using ElementName.
<DataTrigger Binding="{Binding Path=ArisingProperty.PropertyName}"
Value="HideButton">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>

WPF - Making selected listview column editable on button click

I have a listView with a gridview presentation and TextBlocks in each column. I would like to make the selected line editable by replacing the textblocks with TextBoxes and ComboBoxes when the user clicks on an edit button. I tried to do this by setting styles that toggles the visibility of the controls like this :
<Style x:Name="ItemDisplayStyle" TargetType="{x:Type TextBlock}" x:Key="ItemDisplayStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Visibility" Value="{Binding Path=dislayMode}" />
</Style>
<Style x:Name="ItemEditStyle" TargetType="{x:Type FrameworkElement}" x:Key="ItemEditStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Visibility" Value="{Binding Path=editMode}" />
</Style>
displayMode and editMode are Visibility properties set in the code-behind.
And lower in the xaml :
<GridViewColumn Header="Date de début" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Margin="-6,0"
HorizontalAlignment="Stretch" TextAlignment="Center"
Text="{Binding Path=DateDebut, Mode=TwoWay}"
Style="{StaticResource ItemDisplayStyle}" />
<TextBox x:Name="tbDateDebut" Margin="-6,0"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
Text="{Binding Path=DateDebut, Mode=TwoWay}"
Style="{StaticResource ItemEditStyle}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
My problem is that changing 'editMode' and 'displayMode' in the code-behind does not seem to be detected at the UI level.
Also, even if I get this to work, I don't have any idea of how to apply it only to the selected line.
I can do this separately by binding the visibility value with the ListView so that when the user selects a line, she/he gets the editable controls on that line but I really want to allow this only when they click on a button.
Have you Refreshed the contents of the Grid after making changes? You can use the Grid.GetColumn method and send the sender object i.e. edit button (which I suppose would be separate for each column) and then probably use the VisualTreeHelper to get the textbox and combobox in that column.
Also, why don't you use the 'IsReadOnly' property of the TextBox instead of replacing the TextBlock? Make it true or false as per your requirement.

Resources