How to set ItemsPanelTemplate to a dynamically cre

2020-04-05 10:00发布

问题:

I've got this UserControl defined in XAML and would like to set the ItemsPanelTemplate dynamically in my code behind class (not in the XAML like in the example):

<UserControl>
    <ItemsControl x:Name="Items">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid> <!-- I want to add this Grid definition in code behind -->
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                    </Grid.RowDefinitions>
                </Grid>
            </ItemsPanelTemplate>
       </ItemsControl.ItemsPanel>
    </ItemsControl>
</UserControl>

I tried something like

this.Items.ItemsPanel.Template = new Grid();

but failed miserably. Any help?

Background: I only know the number of grid columns and rows at runtime.

回答1:

You need to create an ItemsPanelTemplate and set it's VisualTree to a FrameworkElementFactory (deprecated) which creates the Grid, or use the XamlReader to parse a XAML-string which specifies the template.

This question contains usage examples of both methods (albeit for a different template property).

An easier method to manipulate the panel at runtime is outlined in this question.



回答2:

You can do as you want by creating MannualCode in code behind as: 1. Create a Method as following which will return a ItemsPanelTemplate

     private ItemsPanelTemplate GetItemsPanelTemplate()
    {
        string xaml = @"<ItemsPanelTemplate   xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition />
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition />
                                </Grid.RowDefinitions>
                            </Grid>
                    </ItemsPanelTemplate>";
        return XamlReader.Parse(xaml) as ItemsPanelTemplate;
    }
  1. Now add this template in your Listbox ItemsPanel as:

       MyListBox.ItemsPanel = GetItemsPanelTemplate();
    

This is working fine for me. Hope this will help.

Keep Coding....:)



回答3:

In case that you still have some work to do with the elements, you should take the following (extended) code:

First we need a helper in order to get the element:

// --------------------------------------------------------------------
// This function fetches the WrapPanel from oVisual.
private WrapPanel m_FetchWrapPanel (Visual oVisual)
{
  // WrapPanel to be returned
  WrapPanel oWrapPanel = null;
  // number of childs of oVisual
  int iNumberChilds = VisualTreeHelper.GetChildrenCount (oVisual);
  // and running through the childs
  int i = 0;
  while ( ( i < iNumberChilds ) && ( oWrapPanel == null ) )
  { // fetching visual
    Visual oVisualChild = 
      ( VisualTreeHelper.GetChild (oVisual, i) as Visual );
    if ( ( oVisualChild is WrapPanel ) is true )
    { // found
       oWrapPanel = ( oVisualChild as WrapPanel );
    }
    else
    { // checking the childs of oVisualChild 
      oWrapPanel = m_FetchWrapPanel (oVisualChild);
    };
    // checking next child
    i++;
  };
  // returning WrapPanel
  return (oWrapPanel);
}

Now we create the Panel (or something):

// --------------------------------------------------------------------
private void m_SettingTemplate ()
{
  // the online doc recommends to parse the template
  string xaml = 
    @"<ItemsPanelTemplate
          xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
          xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
        <WrapPanel ItemWidth=""150"" MaxWidth=""150""/>
      </ItemsPanelTemplate>";
  // assigning the template
  oMyListView.ItemsPanel = ( System.Windows.Markup.XamlReader.Parse (xaml) as ItemsPanelTemplate );
  // fetching the WrapPanel
  WrapPanel oWrapPanel = m_WrapPanelAusVisualHolen (oMyListView);
  Debug.Assert (oWrapPanel != null);
  if ( oWrapPanel != null )
  { // adjusting the size of the WrapPanel to the ListView
    Binding oBinding = new Binding ("ActualWidth");
    oBinding.Source = oMyListView;
    oWrapPanel.SetBinding (WrapPanel.MaxWidthProperty, oBinding);
  };
}


回答4:

Here's a XAML based program which uses ItemsPanelTemplate with a Grid:

MainWindow.xaml:

<Window x:Class="WpfTutorialStatusBarGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfTutorialStatusBarGrid"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <DockPanel>

        <StatusBar DockPanel.Dock="Bottom">

            <StatusBar.ItemsPanel>

                <ItemsPanelTemplate>

                    <Grid>

                        <Grid.ColumnDefinitions>

                            <ColumnDefinition Width="100" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="100" />

                        </Grid.ColumnDefinitions>

                    </Grid>

                </ItemsPanelTemplate>

            </StatusBar.ItemsPanel>

            <StatusBarItem Grid.Column="0">
                <TextBlock Name="lblCursorPosition" />
            </StatusBarItem>

            <Separator Grid.Column="1"/>

            <StatusBarItem Grid.Column="2">
                <TextBlock Text="c:\temp\abc.txt"/>
            </StatusBarItem>

            <Separator Grid.Column="3"/>

            <StatusBarItem Grid.Column="4">
                <ProgressBar Value="50" Width="90" Height="16"/>
            </StatusBarItem>

        </StatusBar>

        <TextBox AcceptsReturn="True" Name="txtEditor" SelectionChanged="TxtEditor_SelectionChanged"/>

    </DockPanel>

</Window>

MainWindow.xaml.cs:

using System.Windows;

namespace WpfTutorialStatusBarGrid
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void TxtEditor_SelectionChanged(object sender, RoutedEventArgs e)
        {
            var row = txtEditor.GetLineIndexFromCharacterIndex(txtEditor.CaretIndex);
            var col = txtEditor.CaretIndex - txtEditor.GetCharacterIndexFromLineIndex(row);

            lblCursorPosition.Text = $"Line {row + 1}, Char {col + 1}";
        }
    }
}

It's a simple text editor with a status bar:

Here's the equivalent program with the code in C# instead of XAML:

MainWindow.xaml:

<Window x:Class="WpfTutorialStatusBarGridCs.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfTutorialStatusBarGridCs"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>

    </Grid>
</Window>

MainWindow.xaml.cs:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace WpfTutorialStatusBarGridCs
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var dock_panel = new DockPanel();

            Content = dock_panel;


            var status_bar = new StatusBar();

            dock_panel.Children.Add(status_bar);

            DockPanel.SetDock(status_bar, Dock.Bottom);

            var items_panel_template = new ItemsPanelTemplate();

            {
                var grid_factory = new FrameworkElementFactory(typeof(Grid));

                {
                    {
                        var col = new FrameworkElementFactory(typeof(ColumnDefinition));

                        col.SetValue(ColumnDefinition.WidthProperty, new GridLength(100));

                        grid_factory.AppendChild(col);
                    }

                    {
                        var col = new FrameworkElementFactory(typeof(ColumnDefinition));

                        col.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto));

                        grid_factory.AppendChild(col);
                    }

                    {
                        var col = new FrameworkElementFactory(typeof(ColumnDefinition));

                        col.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Star));

                        grid_factory.AppendChild(col);
                    }

                    {
                        var col = new FrameworkElementFactory(typeof(ColumnDefinition));

                        col.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto));

                        grid_factory.AppendChild(col);
                    }

                    {
                        var col = new FrameworkElementFactory(typeof(ColumnDefinition));

                        col.SetValue(ColumnDefinition.WidthProperty, new GridLength(100));

                        grid_factory.AppendChild(col);
                    }
                }

                items_panel_template.VisualTree = grid_factory;
            }

            status_bar.ItemsPanel = items_panel_template;



            var text_block = new TextBlock();


            {
                var status_bar_item = new StatusBarItem();

                Grid.SetColumn(status_bar_item, 0);

                status_bar_item.Content = text_block;

                status_bar.Items.Add(status_bar_item);
            }

            {
                var separator = new Separator();

                Grid.SetColumn(separator, 1);

                status_bar.Items.Add(separator);
            }

            {
                var status_bar_item = new StatusBarItem();

                Grid.SetColumn(status_bar_item, 2);

                status_bar_item.Content = new TextBlock() { Text = "abc" };

                status_bar.Items.Add(status_bar_item);
            }

            {
                var separator = new Separator();

                Grid.SetColumn(separator, 3);

                status_bar.Items.Add(separator);
            }

            {
                var status_bar_item = new StatusBarItem();

                Grid.SetColumn(status_bar_item, 4);

                status_bar_item.Content = new ProgressBar() { Value = 50, Width = 90, Height = 16 };

                status_bar.Items.Add(status_bar_item);
            }

            {
                var text_box = new TextBox() { AcceptsReturn = true };

                text_box.SelectionChanged += (sender, e) => 
                {
                    var row = text_box.GetLineIndexFromCharacterIndex(text_box.CaretIndex);
                    var col = text_box.CaretIndex - text_box.GetCharacterIndexFromLineIndex(row);

                    text_block.Text = $"Line {row + 1}, Char {col + 1}";
                };

                dock_panel.Children.Add(text_box);
            }
        }
    }
}

The C# version is much more verbose. However, with the help of some extension methods, it can be written in a fluent style, eliminating the intermediate variables:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var text_block = new TextBlock();

        Content = new DockPanel()

            .AddChildren(

                new StatusBar()
                    .SetDock(Dock.Bottom)
                    .SetItemsPanel(
                        new ItemsPanelTemplate()
                            .SetVisualTree(
                                new FrameworkElementFactory(typeof(Grid))
                                    .AppendChildren(
                                        new FrameworkElementFactory(typeof(ColumnDefinition))
                                            .SetValue_(ColumnDefinition.WidthProperty, new GridLength(100)),
                                        new FrameworkElementFactory(typeof(ColumnDefinition))
                                            .SetValue_(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto)),
                                        new FrameworkElementFactory(typeof(ColumnDefinition))
                                            .SetValue_(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Star)),
                                        new FrameworkElementFactory(typeof(ColumnDefinition))
                                            .SetValue_(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto)),
                                        new FrameworkElementFactory(typeof(ColumnDefinition))
                                            .SetValue_(ColumnDefinition.WidthProperty, new GridLength(100)))))
                    .AddItems(
                        new StatusBarItem() { Content = text_block }.SetColumn(0),
                        new Separator().SetColumn(1),
                        new StatusBarItem() { Content = new TextBlock() { Text = "abc" } }.SetColumn(2),
                        new Separator().SetColumn(3),
                        new StatusBarItem() { Content = new ProgressBar() { Value = 50, Width = 90, Height = 16 } }.SetColumn(4)),

                new TextBox() { AcceptsReturn = true }
                    .AddSelectionChanged(
                        (sender, e) =>
                        {
                            var box = sender as TextBox;

                            var row = box.GetLineIndexFromCharacterIndex(box.CaretIndex);
                            var col = box.CaretIndex - box.GetCharacterIndexFromLineIndex(row);

                            text_block.Text = $"Line {row + 1}, Char {col + 1}";
                        }));
    }
}

Here are the extension methods used:

public static class Extensions
{
    public static T SetDock<T>(this T element, Dock dock) where T : UIElement
    {
        DockPanel.SetDock(element, dock);

        return element;
    }

    public static T SetColumn<T>(this T element, int value) where T : UIElement
    {
        Grid.SetColumn(element, value);

        return element;
    }

    public static T SetValue_<T>(this T factory, DependencyProperty dp, object value) where T : FrameworkElementFactory
    {
        factory.SetValue(dp, value);

        return factory;
    }

    public static T AppendChildren<T>(this T factory, params FrameworkElementFactory[] children) where T : FrameworkElementFactory
    {
        foreach (var child in children)
            factory.AppendChild(child);

        return factory;
    }

    public static T SetVisualTree<T>(this T template, FrameworkElementFactory factory) where T : FrameworkTemplate
    {
        template.VisualTree = factory;

        return template;
    }

    public static T1 SetItemsPanel<T1,T2>(this T1 control, T2 template) where T1 : ItemsControl where T2 : ItemsPanelTemplate
    {
        control.ItemsPanel = template;

        return control;
    }

    public static T AddItems<T>(this T control, params object[] items) where T : ItemsControl
    {
        foreach (var item in items)
            control.Items.Add(item);

        return control;
    }

    public static T AddSelectionChanged<T>(this T obj, RoutedEventHandler handler) where T : TextBoxBase
    {
        obj.SelectionChanged += handler;

        return obj;
    }

    public static T1 AddChildren<T1>(this T1 panel, params UIElement[] elements) where T1 : Panel
    {
        foreach (var elt in elements)
            panel.Children.Add(elt);

        return panel;
    }

}