Wpf 在ItemsControl中设置单个CustomControls的名称和访问权限

Wpf 在ItemsControl中设置单个CustomControls的名称和访问权限,wpf,Wpf,我正在用C、.NET4.6和WPF编写一个程序。我希望在运行时以二维网格大小动态指定一组CustomControl,并能够访问每个CustomControl 我做了一些研究,找到了关于ItemsControl的不同信息,并创建了一个解决方案,在某种程度上满足了我的需要。 下面是代码的相关部分,它们编译并运行 自定义控件的XAML <UserControl x:Class="TestApp.MyUserControl" xmlns="http://schemas.m

我正在用C、.NET4.6和WPF编写一个程序。我希望在运行时以二维网格大小动态指定一组CustomControl,并能够访问每个CustomControl

我做了一些研究,找到了关于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>
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();
        }
    }
}
用于托管主窗口的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.Setupint,int用所需数量的MyCustomControls填充ItemControl,我可以标记它们并用我想要的任何颜色填充它们

问题1: 如何实现在指定位置返回MyCustomControl的GetSingleControlint、int?我从一个涉及VisualTreeHelper的解决方案开始,这个解决方案看起来很笨拙而且不灵活

问题2: 如何为第1行和第5列中的项目设置所有MyCustomControl的名称,例如类似MyCustomControl_01_05的名称

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


谢谢大家!

举一个elgonzo和Andy都说的例子,你应该把事情改得更友好。一旦你做了更多的研究,你就会明白为什么你不想为dependencProperties、绑定到后面的代码以及手动编码所有添加的UserControl而烦恼。 这可以变得美观或更精简,但我编写它是为了给出一个完整的示例,说明如何使用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>
添加MainWindowViewModel.cs文件: -注意:如果需要,MyElement可以抽象为UserControl的viewmodel

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:您现在可以访问MyElement列表并相应地更改它们。当您更改任何内容时,UI层应自动更新


问题2:现在不需要这样做,因为每个MyElement都将为其网格位置保留一个属性。因此,您可以直接访问它

想要命名和访问ItemsControl中的某些控件,如果没有错的话,听起来很糟糕。通常不直接在代码隐藏中访问和管理GUI元素,而是将GUI元素绑定到ItemsControl.ItemsSource集合中的某些视图模型项,在代码隐藏中操纵这些视图模型,WPF的绑定机制将允许GUI元素响应/更新,而您的代码不会关心GUI元素的肮脏细节、复杂动态和层次结构/布局。您应该将研究扩展到mvvm。这就是elgonzo所描述的。您的依赖项属性看起来可能不必要。例如,usercontrol已经有一个可以绑定的后台属性。您也可以只使用datatemplate输出UI,而不使用usercontrol。谢谢。我已经怀疑MVVM在这里是个好主意。我犹豫了一下,因为这是一个开销,这只是一个小项目。事实上,这是一个很好的起点,正是我所期望的。非常感谢。
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));
    }
}