Something like a resizeable WrapPannel in WPF

82 views Asked by At

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. GifWindow Here the same with a WrapPanel. Static width and fast wrapping GifWindow

0

There are 0 answers