WPF List control with header - wpf

I'm new to WPF. I trying to display information in a scrollable list/grid that has a header and the user is NOT allowed to click on the items in the list. Also I need a divider between each item in the list.
Right now I'm using a HeaderedContentControl and putting a ListView in the HeaderedContentControl.Content. I can't seem to get the ListView to scroll without setting a specific height on it and it would be nice to have the control be able to resize.
<UserControl ...>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<HeaderedContentControl Margin="10">
<HeaderedContentControl.Header>
<Label ... />
</HeaderedContentControl.Header>
<HeaderedContentControl.Content>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Info1}" />
<TextBlock Text="{Binding Info2}" />
<Separator />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</HeaderedContentControl.Content>
</HeaderedContentControl>
</Grid>
</UserControl>
In the past I've been able to get a ListView scrolling if I put it as a direct child of Grid, but here it's not picking up the height of * as the constraint.

Have you considered using a datagrid? Based on your description I created the following xaml and tested and I believe it satisfies your requirements.
<DataGrid ItemsSource="{Binding Items}" HeadersVisibility="Column"
VerticalScrollBarVisibility="Auto" IsReadOnly="True"
CanUserSortColumns="False" AutoGenerateColumns="False"
IsHitTestVisible="True"
CanUserReorderColumns="False" Height="100"
>
<DataGrid.Columns>
<DataGridTextColumn Header="Header1" Binding="{Binding Info1}"/>
<DataGridTextColumn Header="Header2" Binding="{Binding Info2}"/>
</DataGrid.Columns>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
The IsHitTestVisible must be set false in the cell style and column header style to allow the scroll bar to still work.
Datagrids have a lot more properties which you may be able to exploit to get exactly what you are looking for.

Related

Wpf - Create a control / element as a resource and use it in XAML

I have multiple DataGrid objects in different tabs of WPF window that share the same columns.
I can define a column as a resource like this:
<TabControl.Resources>
<DataGridTextColumn x:Key="NameGridColumn" Binding="{Binding Name}" IsReadOnly="True" Width="*">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name" VerticalAlignment="Center"/>
<Button Command="{Binding SortByNameCommand}" Content="▲" Background="Transparent" BorderThickness="0" Margin="5 0 0 0"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</TabControl.Resources>
But how can I then use these resource columns in a DataGrid?
<TabItem Header="Tab 1">
<DataGrid ItemsSource="{Binding ItemsCollection}" CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<????>
<!-- notice that I have many columns, not just one -->
</DataGrid.Columns>
</DataGrid>
</TabItem>
As I know WPF has restriction - UI element can belong only to one parent control.
So it seems to you can't reuse one column in different grids.
But you can create UserControl and specify in XAML Grid with your columns.
In such case if DataContext will be the same (for example same VM -> you can simple use it in different places of your application).
Not the ideal answer, but fairly good.
This is an answer specific for data grid column headers (the columns you define are not actual controls, but representations for controls that will be generated later). If you want an answer for actual controls in general, you can resort to this answer
You can use a DataTemplate to define the headers:
<TabControl.Resources>
<!--Converter to change arrow buttons appearance-->
<local:EnabledToBrushConverter x:Key="EnabledToBrushConverter"/>
<!--Style for all arrow buttons-->
<Style TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Margin" Value="5 0 0 0"/>
</Style>
<!-- each data template is a header -->
<DataTemplate x:Key="NameGridHeader">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name" VerticalAlignment="Center"/>
<Button Command="{Binding Path=DataContext.SortByNameCommand, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" Content="▲"
Foreground="{Binding Path=DataContext.IsSortByName, Converter={StaticResource EnabledToBrushConverter}, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="AnotherHeader">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Another value" VerticalAlignment="Center"/>
<Button Command="{Binding Path=DataContext.SortByAnotherValueCommand, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" Content="▼"
Foreground="{Binding Path=DataContext.IsSortByAnotherValue, Converter={StaticResource EnabledToBrushConverter}, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}"/>
</StackPanel>
</DataTemplate>
</TabControl.Resources>
And you can use the HeaderTemplate property of a DataGridTextColumn or a DataGridTemplateColumn.
<DataGrid.Columns>
<DataGridTextColumn Header="Type" Binding="{Binding ItemType}" IsReadOnly="True"/>
<DataGridTextColumn HeaderTemplate="{StaticResource NameGridHeader}" Binding="{Binding Name}" IsReadOnly="True" Width="*"/>
<DataGridTextColumn HeaderTemplate="{StaticResource AnotherHeader}" Binding="{Binding AnotherValueString}" IsReadOnly="True"/>
</DataGrid.Columns>
<!-- Define as many datagrids as you like using the same column header templates -->
The downsides are:
Cannot declare the column bindings in the template (might be an advantage if you want the same header for different data, though)
Cannot declare an entire column (header and cell template), must use separate cell templates (with DataGridTemplateColumn's CellTemplate or CellEditingTemplate) or styles if it's the case
Bindings in the headers only work with RelativeSource set to some element above it
There is a way to use DataTemplate for the Cell and another one for the Header:
<DataTemplate x:Key="Head1Template">
<TextBlock Text="Product Name"/>
</DataTemplate>
<DataTemplate x:Key="Head1CellTemplate">
<TextBlock Text="{Binding Product}"/>
</DataTemplate>
and using it in the DataGrid:
<DataGridTemplateColumn CellTemplate="{StaticResource Head1CellTemplate}" HeaderTemplate="{StaticResource Head1Template}"/>
But how can I then use these resource columns in a DataGrid?
You can't. At least not using pure XAML.
What you can to is to define the entire DataGrid, including all columns, as a resource or a separate control and reuse it like any other control, e.g.:
<local:CustomDataGrid ItemsSource="{Binding ItemsCollection}" />
Another option is to redefine the column(s) in each tab but define the StackPanel as a resource and set the header of each column like this:
<DataGridTextColumn Binding="{Binding Name}" IsReadOnly="True" Width="*"
Header="{StaticResource theStackPanel}">
You would then define the StackPanel something like this:
<StackPanel x:Key="theStackPanel" x:Shared="false" Orientation="Horizontal">
<TextBlock Text="Name" VerticalAlignment="Center"/>
<Button Command="{Binding SortByNameCommand}" Content="▲" Background="Transparent" BorderThickness="0" Margin="5 0 0 0"/>
</StackPanel>
Either way, you cannot add a column resource to <DataGrid.Columns> in XAML. You can do it programmatically though:
dataGrid.Columns.Add(tabControl.Resources["NameGridColumn"] as DataGridColumn);

How to achieve the complex UI using ItemControl in silverlight

I am working on creating a complex report which almost looks like shown in here image
For this I have create a collection where I will store all the descriptions and its corresponding ratings.
This collection is then I am binding to a ItemControl. The collection will be fetched from database depending on criteria's.
Now my problem is how to fragment or separate single ItemControl to look like shown in image. Should I use multiple collections which will be then bind to different ItemControl ? Can I use multiple datagrids?
I am out of ideas... Any suggestions / examples much appreciated.
Definitely do-able. Treat each block (such as Mathmatics, Arts Education etc) as an item, and you're then just dealing with an ItemsCollection. Create a style for how to present each item in that collection, and another style for how to present each property in your block (which will also feature a collection of something.
An example I have of something similar, was blocks which consisted of a heading, and a varied number of checkboxes each with a description. There could be a varying number of these blocks too.
In my view, that displayed these blocks, my xaml looked something like this:
<ScrollViewer VerticalScrollBarVisibility="Visible" MaxHeight="100">
<ItemsControl ItemsSource="{Binding FeatureFlags, Mode=TwoWay}" Style="{StaticResource FlagGroupsAndFlagsItemsControlStyle}" />
</ScrollViewer>
I then had a style for that ItemsControl defined in a resource dictionary somewhere ...
<Style x:Key="FlagGroupsAndFlagsItemsControlStyle" TargetType="ItemsControl">
<Setter Property="Width" Value="Auto" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid x:Name="FlagListGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Width="{Binding Width, ElementName=FlagListGrid, Mode=OneWay}" IsReadOnly="True" Text="{Binding Name}" />
<ListBox Grid.Row="1" Width="{Binding Width, ElementName=FlagListGrid, Mode=OneWay}" ItemsSource="{Binding Flags}" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Style="{StaticResource FlagsListBoxItemsStyle}" Background="{Binding IsOptional, Converter={StaticResource YNToOptionalBackgroundColour}}"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
And then a style for the items within that templates ListBox ...
<Style x:Key="FlagsListBoxItemsStyle" TargetType="ListBox">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<toolkit:WrapPanel Width="{Binding Width, ElementName=FlagListGrid, Mode=OneWay}" Orientation="Horizontal" ScrollViewer.HorizontalScrollBarVisibility="Disabled" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="20" Height="18" VerticalAlignment="Top" IsChecked="{Binding IsSelected, Mode=TwoWay}" />
<TextBlock Width="180" MinHeight="18" VerticalAlignment="Center" Text="{Binding Description}" TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Unfortunately I can't show you an image of what the finished result looks like, but I hope these pointers could set you on track for your very similar problem

Datagrid Column Header Templating

I want to template some headercolumns in a pretty simple way, but I don't get the results I am looking for. I want the Border to fill the whole header, not just the textbox inside. Right now I am still having the default gray behind my own controls.
<DataGrid x:Name="grdItems"
Grid.Row="0"
CanUserAddRows="False"
CanUserDeleteRows="False"
ItemsSource="{Binding Path=Items}"
CanUserSortColumns="False" AutoGenerateColumns="False" CanUserResizeColumns="False">
<DataGrid.Columns>
<DataGridComboBoxColumn ItemsSource="{Binding Source={StaticResource GetSignalTypeValues}}" Width="auto" SelectedValueBinding="{Binding SignalType}" >
<DataGridComboBoxColumn.HeaderTemplate>
<DataTemplate>
<Border Background="Orange" >
<TextBlock Text="Signal Type" />
</Border>
</DataTemplate>
</DataGridComboBoxColumn.HeaderTemplate>
</DataGridComboBoxColumn>
<DataGridTextColumn Width="auto" Binding="{Binding AmplitudeMax}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<Border Background="Violet">
<StackPanel>
<TextBlock Text="Amplitude" HorizontalAlignment="Center" />
<TextBlock Text="-maximum-" HorizontalAlignment="Center" />
</StackPanel>
</Border>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
If you are only after background color of column header you can utilize this:
<DataGridComboBoxColumn Width="auto" SelectedValueBinding="{Binding SignalType}" >
<DataGridComboBoxColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="Orange"/>
</Style>
</DataGridComboBoxColumn.HeaderStyle>

Silverlight Textblock inside Listbox causes it to expand instead of wrapping the text

I have something like this:
<ListBox ItemsSource="{Binding List}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Property}" TextWrapping="Wrap" Grid.Column="0"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And the problem I'm having is that the TextBlock will expand the Grid column (and the Listbox) when the text is too long, instead of wrapping it as expected. Maybe I don't understand the star-sizing concept of Grids completely, but the way I see it, since the column width is set to "1*" which means "the remaining space available", the Textblock should NOT try to expand beyond this width, and should wrap the text instead.
So how can I fix this problem? By the way, I need the Grid (or some other container) because there will be other components besides the Textblock. Also the ItemContainerStyle section is there so that the Listbox element occupies the whole space.
Thanks in advance!
Try to add ScrollViewer.HorizontalScrollBarVisibility="Disabled" to your ListBox.
you don't need column definitions if it will be just one, try with the following:
<ListBox ItemsSource="{Binding List}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Property}" TextWrapping="Wrap" HorizontalAlignment="Stretch"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

ComboBox with two columns in popup

I need to create a custom control containing a combobox whose popup will have the Name propeprty of the bound objects aligned to the left, and the CreatedDate property of the bound objects aligned to the right in each pop up item. Also the Name and the CreatedDate must not overlap. The Name of the object is of variable length
I tried solving this problem using a DataTemplate in the Combobox.ItemTemplate, inside the data template I have a grid with two columns aligned appropriately. The Grid's horizontal alignment is set to Stretch but for some reason the Grid doesn't fill out the available space in the popup. Does anyone know how to get around this and why it happens? I am using WPF 3.5.
<UserControl.Resources>
<CollectionViewSource x:Key="cvsProcessingSessionList" Source="{Binding ProcessingSessionList, ElementName=View}"/>
<Style TargetType="{x:Type ComboBox}"
BasedOn="{StaticResource {x:Static res:ObjectResources.LEComboBoxStyle}}">
<Setter Property="Margin" Value="5,3"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<GroupBox x:Name="grbProcessingSession">
<GroupBox.Header>
<Bold>Processing Session:</Bold>
</GroupBox.Header>
<GroupBox.Content>
<ComboBox
x:Name="ccbProcessingSessionName"
ItemsSource="{Binding Source={StaticResource cvsProcessingSessionList}}"
Loaded="OnLoaded" TextSearch.TextPath="Name"
IsEditable="True" MaxDropDownHeight="500" >
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Background="Pink" HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextAlignment="Left" HorizontalAlignment="Left"
Margin="0,0,5,0" Text="{Binding Name}"/>
<TextBlock Grid.Column="1" TextAlignment="Right" HorizontalAlignment="Right"
Text="{Binding CreatedDate, StringFormat=dd/MM/yyyy}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Content>
</GroupBox>
Just add this binding to your grid in yiour DataTemplate ...
Width="{Binding ActualWidth,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ComboBoxItem}},
Mode=OneTime}"
Also for better effect, apply the background color to the ComboBoxItem and not to the grid...
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Background" Value="Pink"/>
</Style>
</ComboBox.ItemContainerStyle>
Whenever I want to do the same that you are asking I add:
<ComboBox ...>
<ComboBoxItem HorizontalContentAlignment="Stretch">
</ComboBoxItem>
</ComboBox>
The thing is that I place my custom DataTemplate inside the ComboBoxItem.
Maybe you should try it...
Actually, the better way is to set HorizontalContentAlignment="Stretch" at the ComboBox level. This will work even if width of ComboBox changes (e.g. if it is in resizable container).
However, be aware that this works in WPF and Silverlight 5 but don't work in some older versions of Silverlight.

Resources