首页 > 解决方案 > 在 ItemsControl 中设置单个自定义控件的名称和访问权限

问题描述

我正在用 C#、.NET 4.6 和 WPF 编写程序。我希望将一组自定义控件排列在二维网格中(在运行时动态指定大小),并且能够访问每个自定义控件。

我做了一些研究,找到了有关 ItemsControl 的不同信息,并创建了一个解决方案,它在某种程度上可以满足我的需求。这是代码的相关部分,它们编译并运行。

自定义控件的 XAML

<UserControl x:Class="TestApp.MyUserControl"
             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" 
             xmlns:local="clr-namespace:TestApp"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Rectangle Fill="{Binding MyFill1, RelativeSource={RelativeSource FindAncestor, AncestorType=local:MyUserControl}}">
        </Rectangle>
        <Viewbox>
            <TextBlock Text="{Binding MyText1, RelativeSource={RelativeSource FindAncestor, AncestorType=local:MyUserControl}}" >
            </TextBlock>
        </Viewbox>
    </Grid>
</UserControl>

CustomControl 的代码隐藏

namespace TestApp
{
    public partial class MyUserControl : UserControl
    {

        public static readonly DependencyProperty MyText1Property =
            DependencyProperty.Register("MyText1", 
                                        typeof(String), typeof(MyUserControl),
                                        new PropertyMetadata(""));

        public String MyText1
        {
            get { return (String)GetValue(MyText1Property); }
            set { SetValue(MyText1Property, value); }
        }

        public static readonly DependencyProperty MyFill1Property =
            DependencyProperty.Register("MyFill1",
                                        typeof(SolidColorBrush),
                                        typeof(MyUserControl),
                                        new PropertyMetadata(new SolidColorBrush(Colors.Green)));

        public SolidColorBrush MyFill1
        {
            get { return (SolidColorBrush)GetValue(MyFill1Property); }
            set { SetValue(MyFill1Property, value); }
        }


        public MyUserControl()
        {
            InitializeComponent();
        }
    }
}

用于托管 MainWindow 的 XAML

<Window x:Class="TestApp.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:TestApp"
        mc:Ignorable="d"
        Name="MyMainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ItemsControl Name="MyItemsControl">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Columns="{Binding ElementName=MyMainWindow, Path=UniformGridColumns, Mode=OneWay}" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <local:MyUserControl MyText1="{Binding Text1}" MyFill1="{Binding Fill1}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

用于托管主窗口的代码隐藏

namespace TestApp
{
    public partial class MainWindow : Window
    {
        public int UniformGridColumns  //number of columns of the grid
        {
            get { return (int)GetValue(UniformGridColumnsProperty); }
            set { SetValue(UniformGridColumnsProperty, value); }
        }

        public static readonly DependencyProperty UniformGridColumnsProperty =
            DependencyProperty.Register("UniformGridColumns", typeof(int), typeof(MainWindow),
                new FrameworkPropertyMetadata((int)0));

        public MainWindow()
        {
            InitializeComponent();
            //this.DataContext = this;
            Setup(13, 5); //13 columns, 5 rows
        }

        public void Setup(int columns, int rows) //setup the grid
        {
            UniformGridColumns = columns;
            SingleControl[] singleControls = new SingleControl[rows*columns];

            for (int i = 0; i < rows*columns; i++)
                singleControls[i] = new SingleControl()
                {
                    Text1 = (i/ columns + 1) + ", " + (i % columns + 1),
                    Fill1 = new SolidColorBrush((i % 2 != 0) ? Colors.Yellow : Colors.Red)
                }; //example, display position in grid and fill with two different colours

            MyItemsControl.ItemsSource = singleControls.ToList<SingleControl>();
        }

        public MyUserControl GetSingleControl(int column, int row)  //access a single control
        {
            //some code involving VisualTreeHelper
            return null;
        }

        private class SingleControl  //helper class for setting up the grid
        {
            public String Text1 { get; set; }
            public Brush Fill1 { get; set; }
        }
    }
}

MainWindow.Setup(int, int) 方法用所需数量的 MyCustomControls 填充 ItemControl,我可以用我想要的任何颜色标记和填充它们。

问题 1:如何实现在指定位置返回 MyCustomControl 的 GetSingleControl(int, int)?我从一个涉及 VisualTreeHelper 的解决方案开始,该解决方案似乎笨拙且不灵活。

问题 2:如何为第 1 行和第 5 列中的项目设置所有 MyCustomControls 的名称,例如“MyCustomControl_01_05”。

问题3:如果根据我的解决方案无法回答问题1和2,那么更合适的方法是什么?

谢谢!

标签: wpf

解决方案


举一个 elgonzo 和 Andy 所说的例子,你应该改变一些东西,使其对 MVVM 更友好。一旦你做更多的研究,你就会明白为什么你不想打扰 DependencyProperties,绑定到后面的代码,并手动编码用户控件的所有添加。这可以做得很漂亮或更精简,但我对其进行了编码,以提供一个完整的示例,说明如何使用 MVVM 完成此操作。我试图使它简单和基本,同时演示如何重构你的想法。

新的 MainWindow.xaml

 <Window x:Class="TestApp.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:TestApp"
    d:DataContext="{d:DesignInstance {x:Type local:MainWindowViewModel}}"
    mc:Ignorable="d"
    Name="MyMainWindow"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
    <ItemsControl Name="MyItemsControl" ItemsSource="{Binding MyList}">
        <ItemsControl.ItemContainerStyle>
            <Style>
                <Setter Property="Grid.Row" Value="{Binding GridRow}" />
                <Setter Property="Grid.Column" Value="{Binding GridColumn}" />
            </Style>
        </ItemsControl.ItemContainerStyle>

        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Columns="{Binding ColumnCount}" Rows="{Binding RowCount}" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Rectangle Fill="{Binding Fill1}"/>
                    <TextBlock Text="{Binding Text1}"/>
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

新建 MainWindow.xaml.cs(注意没有额外的代码)

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

添加文件 MainWindowViewModel.cs:-注意,如果您愿意,可以将 MyElement 抽象为 UserControl 的视图模型。

public class MyElement : INotifyPropertyChanged
{
    public MyElement()
    {
        //some default data for design testing
        Text1 = "Test";
        Fill1 = new SolidColorBrush(Colors.Red);
        GridColumn = 13;
        GridRow = 5;
    }
    private string _text1;
    public string Text1
    {
        get { return _text1; }
        set{
            if (value != _text1) { _text1 = value; RaisePropertyChanged(); }
        }
    }

    private Brush _fill1;
    public Brush Fill1
    {
        get { return _fill1; }
        set
        {
            if (value != _fill1) { _fill1 = value; RaisePropertyChanged(); }
        }
    }
    private int _gridRow;
    public int GridRow
    {
        get { return _gridRow; }
        set
        {
            if (value != _gridRow) { _gridRow = value; RaisePropertyChanged(); }
        }
    }

    private int _gridColumn;
    public int GridColumn
    {
        get { return _gridColumn; }
        set
        {
            if (value != _gridColumn) { _gridColumn = value; RaisePropertyChanged(); }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
public class MainWindowViewModel : INotifyPropertyChanged
{
    public MainWindowViewModel() : this(13, 5) { }

    public MainWindowViewModel(int columns, int rows)
    {
        ColumnCount = columns;
        RowCount = rows;

        MyList = new ObservableCollection<MyElement>();

        //your original setup code
        for (int i = 0; i < columns; i++)
        {
            for (int j = 0; j < rows; j++)
            {
                var vm = new MyElement
                {
                    Text1 = (i / columns + 1) + ", " + (i % columns + 1),
                    Fill1 = new SolidColorBrush((i % 2 != 0) ? Colors.Yellow : Colors.Red),
                    GridColumn = i,
                    GridRow = j
                };
                MyList.Add(vm);
            }
        }
    }

    private int _rowCount;
    public int RowCount
    {
        get { return _rowCount; }
        set
        {
            if (value != _rowCount) { _rowCount = value; RaisePropertyChanged(); }
        }
    }

    private int _columnCount;
    public int ColumnCount
    {
        get { return _columnCount; }
        set
        {
            if (value != _columnCount) { _columnCount = value; RaisePropertyChanged(); }
        }
    }
    public ObservableCollection<MyElement> MyList { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

我做了一个更完整的解决方案,它使用 INotifyPropertyChanged。我不会解释使用它的原因(如果您不知道),因为您可以快速搜索更好的解释。

我也这样做了,所以所有动态信息都使用绑定来使事情更容易改变。现在网格大小和项目定位已绑定到您的数据。因此,当您更改“MyElement”时,它应该会自动调整

这应该为您重构代码提供了一个很好的起点,并帮助您利用 WPF 的设计目的,因为内置了许多机制,因此您不必对 UI 层操作进行硬编码(就像您在后面的代码中一样)

这也回答了您的问题:

Q1:您现在可以访问 MyElements 列表并相应地更改它们。当您更改任何内容时,UI 层应该会自动更新。

Q2:您现在不需要这样做,因为每个 MyElement 都会为其 Grid Position 保留一个属性。因此,您可以访问它。


推荐阅读