设置名称并访问ItemsControl中的各个CustomControls

时间:2019-05-10 14:57:33

标签: wpf

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

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

XAML for CustomControl

<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: 如何设置所有MyCustomControl的名称,例如第1行和第5列中的项目类似“ MyCustomControl_01_05”。

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

谢谢!

1 个答案:

答案 0 :(得分:1)

举例说明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的情况下,我做了一个更完整的解决方案。我不会解释使用它的原因(如果您不知道的话),因为您可以快速搜索到更好的解释。

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

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

这还会回答您的问题:

Q1:现在,您可以访问MyElements列表并进行相应更改。更改任何内容后,UI层应自动更新。

Q2:您现在不需要执行此操作,因为每个MyElement都会为其网格位置保留一个属性。这样您就可以访问它。