I am looking for a wrappanel or rather something that works similarly. The wrappanel grabs the Uielement in a new line, but the elements are not dynamic in width, but static. The setting »HorizontalAlignment=" Stretch"« does not work and the elements look squeezed together. That means you have to put the width manually »Width="200"« and that is very static. So when I change the width of my window, more elements come into one line, but the existing elements do not adapt to the width of the window. I would have liked to fill out the elements for the entire width. Overall, it also sees much better.
I made a user control that fits my purpose but it can’t be virtualized and is really slow with the resize. Here is my code for the adaptable UserControl.
<UserControl x:Class="ItemViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
Loaded="UserControl_Loaded"
x:Name="uc">
<Grid DataContext="{Binding ElementName=uc}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"
x:Name="SecondRow" />
</Grid.RowDefinitions>
<TextBlock x:Name="Header"
Text="{Binding HeaderText,Mode=OneWay}"
Foreground="White"
HorizontalAlignment="Left"
Margin="0,0,0,10"
FontSize="28"
FontWeight="Medium" />
<ItemsControl Grid.Row="2"
x:Name="ListViewProducts"
SizeChanged="Grid_SizeChanged"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Items,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="{Binding Path=DataContext.ItemHeight, RelativeSource={RelativeSource AncestorType=ItemsControl},Mode=OneTime}"
MaxWidth="{Binding Path=DataContext.ItemMaxWidth, RelativeSource={RelativeSource AncestorType=ItemsControl},Mode=OneTime}"
Margin="0,0,10,10">
<Border Background="Beige"
CornerRadius="10" />
<!-- The specific UserControl or an element -->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2"
Initialized="UniformGrid_Initialized" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="VerticalAlignment"
Value="Top" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</UserControl>
The code behind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interop;
namespace MVVM.View.HomeView
{
/// <summary>
/// Interaktionslogik für ItemViewer.xaml
/// </summary>
public partial class ItemViewer : UserControl
{
private UniformGrid? _itemUniformGrid;
public static readonly DependencyProperty HeaderTextProperty = DependencyProperty.Register(nameof(HeaderText), typeof(string), typeof(ItemViewer));
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(ItemViewer));
public double ItemHeight
{
get { return (double)GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
public static readonly DependencyProperty ItemMinWidthProperty = DependencyProperty.Register("ItemMinWidth", typeof(double), typeof(ItemViewer));
public double ItemMinWidth { get { return (double)GetValue(ItemMinWidthProperty); } set { SetValue(ItemMinWidthProperty, value); List_SizeChanged(); } }
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(ItemViewer));
public double ItemWidth { get { return (double)GetValue(ItemWidthProperty); } set { SetValue(ItemWidthProperty, value); List_SizeChanged(); } }
public static readonly DependencyProperty ItemMaxWidthProperty = DependencyProperty.Register("ItemMaxWidth", typeof(double), typeof(ItemViewer));
public double ItemMaxWidth { get { return (double)GetValue(ItemMaxWidthProperty); } set { SetValue(ItemMaxWidthProperty, value); List_SizeChanged(); } }
private double row = 0;
public double Row { get { return row; } set { row = (value >= 0 ? value : 0); if (Row != 0) { SecondRow.MaxHeight = 10 * (Row - 1) + ItemHeight * Row; } } }
public ItemViewer()
{
InitializeComponent();
}
private int itemCounter = 0;
public ObservableCollection<object> Items
{
get { return (ObservableCollection<object>)GetValue(ItemsProperty); }
set => SetValue(ItemsProperty, value);
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<object>), typeof(ItemViewer));
private const int WmExitSizeMove = 0x232;
private IntPtr HwndMessageHook(IntPtr wnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WmExitSizeMove:
List_SizeChanged();
handled = true;
break;
}
return IntPtr.Zero;
}
private void List_SizeChanged()
{
if (Items == null || Items.Count == 0 || _itemUniformGrid == null)
return;
itemCounter = Items.Count;
int columns = (int)(ListViewProducts.ActualWidth / (10 + ItemWidth));
if (columns <= itemCounter && ListViewProducts.ActualWidth < (ItemMaxWidth + 10) * columns)
{
if (_itemUniformGrid.HorizontalAlignment != HorizontalAlignment.Stretch)
{
_itemUniformGrid.ClearValue(WidthProperty);
_itemUniformGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
}
if (columns != _itemUniformGrid.Columns)
_itemUniformGrid.Columns = columns;
}
else
{
if (columns >= itemCounter)
{
double newWidth;
newWidth = ListViewProducts.ActualWidth / itemCounter;
newWidth = newWidth > ItemMaxWidth ? ItemMaxWidth : newWidth;
_itemUniformGrid.Width = (newWidth + 10) * itemCounter;
_itemUniformGrid.HorizontalAlignment = HorizontalAlignment.Left;
_itemUniformGrid.Columns = itemCounter;
}
else
{
_itemUniformGrid.HorizontalAlignment = HorizontalAlignment.Left;
_itemUniformGrid.Columns = (int)(ListViewProducts.ActualWidth / (ItemMaxWidth + 10));
_itemUniformGrid.Width = (ItemMaxWidth + 10) * _itemUniformGrid.Columns;
}
}
}
private void UniformGrid_Initialized(object sender, EventArgs e) =>
_itemUniformGrid = (UniformGrid)sender;
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
List_SizeChanged();
if (Application.Current.MainWindow == null)
return;
WindowInteropHelper? helper = new(Application.Current.MainWindow);
HwndSource? source = HwndSource.FromHwnd(helper.Handle);
if (source != null)
source.AddHook(HwndMessageHook);
}
private int sizeP = 0;
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (sizeP - e.NewSize.Width is < (-200) or > 200)
{
sizeP = (int)e.NewSize.Width;
List_SizeChanged();
}
else if (Application.Current.MainWindow.WindowState == WindowState.Maximized && sizeP != (int)e.NewSize.Width)
{
sizeP = (int)e.NewSize.Width;
List_SizeChanged();
}
}
}
}
Implementation:
<local:ItemViewer Items="{Binding Objects}"
HeaderText="Recent"
ItemMinWidth="200"
ItemWidth="350"
ItemHeight="150"
ItemMaxWidth="500"
Row="1" />
To make it more responsive I call the resized only when the resize finished. I think it doesn't break with mvvm because it is only for the UI. But in the end, it is quite a bad implementation. So, is there a better way or good code on GitHub that has a similar behaviour? Here are a few Images of an implementation of my code: The Width is set dynamically and it wraps when there is enough space.
Here my implementation of my UserControl. The Broders have a dynamic width and also can wrap, but is realy slow.
Here the same with a WrapPanel. Static width and fast wrapping
