I have to create a control which meets the following requirements:
Items are displayed in cells of a "table"
Items are selectable
Items are drag n droppable
I have 2 approaches:
Using an ItemsControl with a Grid as it's ItemsPanelTemplate
Big problem: ItemsControl doesnt support selection out of the box
Use a DataGrid (seems obscure to me, since it's "not designed for this")
Here's the ItemsControl I am speaking of:
<ItemsControl Grid.Column="1" Grid.Row="2" Margin="5,5,5,0" BorderThickness="1" BorderBrush="Gray" Name="ItemsRack">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding RowPos}" />
<Setter Property="Grid.Column" Value="{Binding ColPos}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Items are displayed in cells of a "table"
You have a couple options:
Use DataGrid: It does have "cells" and provides some features for editing them. If you get to know it well, you can do even more complex stuff.
Create your own? This ensures you are in control of everything and know how everything works. If this is a big project, I'd go with this option. Study the DataGrid and emulate it. If you run into roadblocks, check out third-party solutions and try to learn from them.
Go third-party: Xceed has a free DataGrid that provides more features than the standard .NET one probably ever will. Cells are definitely present and the features are seemingly enough.
http://wpftoolkit.codeplex.com/
Items are selectable
ListBox/ListView is a reasonable alternative, but probably not what you're looking for.
ItemsControl is limited in what it can do and does NOT support item selection.
DataGrid: Your best bet. Refer to first response.
Items are drag n droppable
Look into GongSolutions' drag/drop library. The rumors really are true, it works wonders.
https://github.com/punker76/gong-wpf-dragdrop
Keep in mind, if you decide to roll out your own DataGrid, it is no simple task. Fun, but difficult.
Related
I am experiencing some behaviour in a UWP project that I am unable to understand, however my implementations of this in WPF work as expected.
If I have a StackPanel, and insert some grids, the grids stretch to fill the width of the StackPanel as expected.
<StackPanel Grid.Row="1">
<Grid Background="Blue">
<TextBlock>Item 1</TextBlock>
</Grid>
<Grid Background="Green">
<TextBlock>Item 2</TextBlock>
</Grid>
<Grid Background="Red">
<TextBlock>Item 3</TextBlock>
</Grid>
<Grid Background="Pink">
<TextBlock>Item 4</TextBlock>
</Grid>
</StackPanel>
However, once I try to introduce binding and collections, this behaviour stops happening.
<ItemsControl ItemsSource="{Binding Devices}" Grid.Row="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Background="Violet">
<TextBlock>Hello</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have found similar issues reported dating back to 2010, but none of the solutions have worked thus far. I have also found issues reported here for UWP Grid generally, used in other contexts. I have tried using a ListBox (which behaves the same in this regard), setting HorizontalContentAlignment, using styles as Silverlight users seem to have found joy in doing so, and changing how I express my ItemsControl. Nothing has worked thus far, and I am feeling that there is something wrong with XAML in UWP as I don't seem to experience any issues at all in WPF. Is there a known problem, or am I simply going about this the wrong way?
I simply want the child grid to stretch to fill horizontally. I should mention:
It does behave if I wrap the grid in a ViewBox, but this is not desirable as it has other adverse effects.
If I click on everything down the hierarchy in XAML, starting at ItemsControl, the box rendered around this selection is full width all the way down to (excluding) DataTemplate. DataTemplate and Grid show a tight-bound box, but if I look in the property inspector the Grid has its horizontal alignment as stretch. This leads me to believe that the DataTemplate is wrapping the grid with something that doesn't have a HorizontalAlignment="Stretch", and I have no known mechanism to change this.
This behaviour seems to only appear in the designer of Visual Studio and Expression Blend. Once built and executing on both Phone and Desktop, everything appears to render correctly.
I've generally had very good experiences with the designer, and did not expect this to be the case. Had I just ignored the designer and progressed, I would have realised this sooner. I have reported this issue via the VisualStudio feedback tool.
I have a DataGrid that has a CollectionViewSource bound to it's ItemSource Property:
<DataGrid Grid.Row="0" RowBackground="#10808080" AlternatingRowBackground="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{Binding Source={StaticResource bookingsViewSource}}"
RowHeight="27"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.IsContainerVirtualizable="True"
VirtualizingPanel.ScrollUnit="Item"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding date, StringFormat=dd.MM.yyyy}" Header="date"/>
<DataGridTextColumn Binding="{Binding Path=customers.name}" Header="customer"/>
<DataGridTextColumn Binding="{Binding Path=customers.street}" Header="adress"/>
</DataGrid.Columns>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding Path=Name}" IsExpanded="True">
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
bookingsViewSource is defined as
<CollectionViewSource x:Key="bookingsViewSource"
d:DesignSource="{d:DesignInstance {x:Type Database:bookings}}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="providerID"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
and get's filled in code behind section. Everything was doing fine fast and smooth without grouping. But when I added grouping <PropertyGroupDescription PropertyName="providerID"/> the DataGrid needs around one minute to load.
In .NET 4.5 there is a new Property called VirtualizingPanel.IsVirtualizingWhenGrouping and I already set this to true but loading time was not decreasing.
I can not figure out why. Any ideas?
From the book: MacDonald M. - Pro WPF 4.5 in C#
A number of factors can break UI virtualization, sometimes when you don’t expect it:
Putting your list control in a ScrollViewer: The ScrollViewer provides a window onto
its child content. The problem is that the child content is given unlimited “virtual”
space. In this virtual space, the ListBox renders itself at full size, with all of its child
items on display. As a side effect, each item gets its own memory-hogging
ListBoxItem object. This problem occurs any time you place a ListBox in a container
that doesn’t attempt to constrain its size; for example, the same problem crops up if
you pop it into a StackPanel instead of a Grid.
Changing the list’s control template and failing to use the ItemsPresenter: The
ItemsPresenter uses the ItemsPanelTemplate, which specifies the
VirtualizingStackPanel. If you break this relationship or if you change the
ItemsPanelTemplate yourself so it doesn’t use a VirtualizingStackPanel, you’ll lose
the virtualization feature.
Not using data binding: It should be obvious, but if you fill a list programmatically—
for example, by dynamically creating the ListBoxItem objects you need—no
virtualization will occur. Of course, you can consider using your own optimization
strategy, such as creating just those objects that you need and only creating them at
the time they’re needed. You’ll see this technique in action with a TreeView that uses
just-in-time node creation to fill a directory tree in Chapter 22.
If you have a large list, you need to avoid these practices to ensure good performance.
Also, in my case the issue was caused by MahApps.Metro styles.
I'd like to arrange some TextBlocks horizontally without any margins. I can see that my TextBlock is as small as it should be, but for some reason a lot of space is added around it. I think it has something to do with the ListView or its styling, but I don't know what.
I have a following layout:
<ListView Width="Auto" SelectionMode="None" Background="#654321" ItemsSource="{Binding}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Border Background="Black">
<TextBlock Text="{Binding}" Margin="0,0,0,0"/>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
<x:String>A</x:String>
<x:String>B</x:String>
<x:String>C</x:String>
<x:String>D</x:String>
<x:String>E</x:String>
</ListView>
What you need to change is the ItemContainerStyle. You can get the Style from here.
The big thing to notice is that the default Margin of the Style is:
<Setter Property="Margin" Value="0,0,18,2"/>
Which explains the large gap. Change it to:
<Setter Property="Margin" Value="0,0,0,2"/>
And that should help solve your issue.
One last thing to note is that they also have a default ContentMargin="4" (if using the ListViewItemsPresenter) or a bunch of Margins of 4 spread throughout the style (especially on the Borders), which you may need to change as well.
The other way to look at it - ListView is really designed as a control where you can tap, select, drag and otherwise interact with the items. I think if you want to remove all the features that ensure the sizes are touchable with fat fingers - you might just be better off using something like the base ItemsControl or ListBox which might not have these limitations. If you get rid of all the margins and leave your items so small - there's no point in using the ListView and it will just complicate things and make performance bad.
I have a DataGrid which I am using with an ICollectionView to group items in a large collection, 20k+ rows in some instances. I have used this approach before with varying success with showing all rows or virtualizing to create a more responsive page. In this instance I would like to virtualize as much as possible to keep the UI responsive. I have used the tips in this answer with little success to my issues.
Helpful DataGrid Link
My main issue is a couple second lag on the DataGrid when loading the data into the ICollectionView View/Source, which I would like to minimize with the proper virtualization. Here is some of my code:
<DataGrid Margin="0,2,0,0" IsReadOnly="True" ItemsSource="{Binding DataView,IsAsync =True}" EnableRowVirtualization ="True" MaxWidth="2560" MaxHeight="1600"
Grid.Row="2" SelectionMode="Extended" VirtualizingPanel.IsVirtualizingWhenGrouping="True" SelectionUnit="FullRow" SelectedItem="{Binding SelectedOutage}">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Foreground="{StaticResource Foreground}" Background="{StaticResource AlternatingBackground}">
<Expander.Header>
<TextBlock Text="{Binding Name}" Margin="5,0,0,0" Width="300"/>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<!--Follows are DataGrid.ContextMenu and DataGridTextColumns with fixed widths-->
</DataGrid>
And c# items:
public ICollectionView DataView { get; set; }
private readonly ObservableCollection<EquipmentMonitorRow> equipment = new ObservableCollection<EquipmentMonitorRow>();
DataView = CollectionViewSource.GetDefaultView(equipment);
DataView.GroupDescriptions.Add(new PropertyGroupDescription("GroupName"));
equipment.Clear();
//Lag is during this item adding.
equipment.AddRange(data);
So hopefully I'm missing some virtualization or maybe I can add the items differently or something.
Any help would be appreciated. Thanks.
Changing my ObservableCollection source to a List source solved the initial load lag.
private readonly List<EquipmentMonitorRow> equipment = new List<EquipmentMonitorRow>();
Also using a combination of VirtualizingPanel properties I achieved the best virtualization. Specifically if I omitted VirtualizingPanel.IsVirtualizingWhenGrouping="True" then the application lagged for almost a minute before showing anything in my DataGrid. The scrolling was a lot better without virtualization but the initial load was unacceptable in my case.
VirtualizingPanel.IsVirtualizingWhenGrouping="True" VirtualizingPanel.ScrollUnit ="Item" VirtualizingPanel.VirtualizationMode="Recycling"
Thanks for the help.
Datagrid by default support UI virtualization but as soon as you apply grouping on ICollectionView, UI virtualization will be turned off.
You can refer to MSDN sample which basically flattens the grouped list which supports virtualization.
UPDATE for comment:
This doesn't specifically mention a collectionview but seems there is
virtualization
From the link:
In a normal WPF ItemsControl that virtualizes, such as ListBox or
ListView, virtualization turns off when you turn grouping on.
And DataGrid derives from ItemsControl only, hence UI virtualization is turned off on grouping.
I have a list of objects. For each item in the list, I want to create a new user control bound to that item. From what I've read, doing this programmatically is bad practice with WPF (as well as less than straightforward), so I should use data binding as a solution instead. The problem is, I can't figure out how to do this. I don't know the contents of the list (just the type) at compile-time, so I can't create and bind with XAML for each element. Google and MSDN don't seem to have any answers, so maybe I'm thinking about this the wrong way? What do I need to do?
Thanks
EDIT: To clarify, I'm trying to make my own music scoring software, something like Rosegarden. The list would contain all of the measures, and the usercontrols would be their visual representation.
A more generic approach than Julien Lebosquain's suggestion (and one that will work when the list of items contains objects of more than one data type):
Create a DataTemplate to be used in presenting an item of the type(s) in your list, e.g.:
<DataTemplate DataType="local:Measure">
<local:MeasureUserControl DataContext="{Binding}"/>
</DataTemplate>
Use an ItemsControl to present the items:
<ItemsControl ItemsSource="{Binding MeasureList}"/>
You can set the ItemsPanel property of the ItemsControl to an ItemsPanelTemplate to govern how it will lay out the user controls, e.g.:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
This approach is generally preferable to using a ListBox when you don't want the features of the ListBox, e.g. its default border and selection behavior.
You can use a standard ListBox with a custom item style:
Somewhere in the resources:
<Style TargetType="{x:Type ListBoxItem}" x:Key="CustomItemStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<yourns:YourControl />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In your window/page/usercontrol:
<ListBox ItemsSource="{Binding ...}" ItemContainerStyle="{StaticResource CustomItemStyle}" />
Since your objects will be bound to the listbox, an implicit ListBoxItem will be created for each object, with its DataContext set to the object so you can use bindings in YourControl without any worries.
All the above answers work, but I'll leave with how I'm doing this in my application.
I'm implementing the MVVM architecture that takes advantage of these WPF features.
This is a UserControl I'm using that has an ItemsControl populated with the items of a certain type:
<UserControl x:Class="Controls.StepView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:Controls"
Background="Transparent">
<UserControl.Resources>
<DataTemplate DataType="{x:Type my:ParameterViewModel}" >
<my:ParameterView HorizontalAlignment="Stretch" Margin="25 0 0 0"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl Name="stkStepContent" ItemsSource="{Binding Parameters}" />
</Grid>
</UserControl>
Let me explain the code for you. in the DataTemplate section I say that I want to render objects of class ParameterViewModel with the UserControl ParameterView. The ItemsSource property of my ItemsControl is binded to a List<ParameterViewModel>. When the ItemsControl is initiated for each ParameterViewModel on the List it will create a ParameterView and set its DataContext to the ParameterViewmodel it is rendering.
I found that this architectural pattern is the most intuitive for me to build WPF applications.