wpf - 在 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,那么更合适的方法是什么?
谢谢!
解决方案
举一个 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 保留一个属性。因此,您可以访问它。
推荐阅读
- javascript - 如何在模板文字中使用模板文字(``)?
- php - 当您无权访问服务器时,http 到 https 的重定向
- android - 无法在电话间隙上编译应用程序 - 新错误与贬值
- javascript - 为什么我的长十进制数在 javascript 中显示为“NaN”
- r - 如何使用日志将变量“population_in_millions”转换为以 7 为底的转换
- jetty - 如何在 geoserver 旁边添加第二个带有 jetty 的 Web 应用程序?
- ios - 创建一个精灵并给它一个自定义类
- amazon-web-services - 如何将 AWS CLI 的输出保存在变量中?
- sql - 如何获取客户数量?
- mysql - Spring Security:注册多个用户时出现 MySQL 重复条目错误