【问题标题】:How do I bind a simple array?如何绑定一个简单的数组?
【发布时间】:2011-07-25 09:33:32
【问题描述】:

我正在用 WPF C# 制作一个类似拼字游戏的游戏。截至目前,我已经让 AI 算法在一个“模型游戏板”上运行,一个变量字符串 [15,15]。但是在过去的几天里,我一直在制作一个 GUI 来将此数组显示为游戏板。

截至目前,我有以下 XAML 代码:

我的“主窗口”包含:

一个按钮:

<Button Click="Next_TurnClicked" Name="btnNext_Turn">Next Turn</Button>

A UserControl:哪个是游戏板(GameBoard 也是另一个 UserControl)和 Player's Rack

<clr:Main_Control></clr:Main_Control>

然后在我的 UserControl 中我有:

    <DockPanel Style ="{StaticResource GradientPanel}">
    <Border Style ="{StaticResource ControlBorder}" DockPanel.Dock="Bottom">
        <ItemsControl VerticalAlignment="Center" HorizontalAlignment="Center">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Height ="Auto" Name="ListBox" Orientation="Horizontal" HorizontalAlignment="Center">
                    </StackPanel>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <clr:Rack_Cell_Sender x:Name="Player_Tile_1" ></clr:Rack_Cell_Sender>
            <clr:Rack_Cell_Sender x:Name="Player_Tile_2" ></clr:Rack_Cell_Sender>
            <clr:Rack_Cell_Sender x:Name="Player_Tile_3" ></clr:Rack_Cell_Sender>
            <clr:Rack_Cell_Sender x:Name="Player_Tile_4" ></clr:Rack_Cell_Sender>
            <clr:Rack_Cell_Sender x:Name="Player_Tile_5" ></clr:Rack_Cell_Sender>
        </ItemsControl>
    </Border>
    <Viewbox Stretch="Uniform">
        <Border Margin="5" Padding="10" Background="#77FFFFFF" BorderBrush="DimGray" BorderThickness="3">
            <Border BorderThickness="0.5" BorderBrush="Black">
                <clr:GameBoard>
                </clr:GameBoard>
            </Border>
        </Border>
    </Viewbox>
</DockPanel>

clr:GameBoard 是一个 ItemsControl,其 ItemsPanelTemplate 为 UniformGrid

        <Style TargetType="ItemsControl">
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <UniformGrid IsItemsHost="True" Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Rows="15" Columns="15" />
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Words:Cell>
                    </Words:Cell>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</UserControl.Resources>
<ItemsControl Name="BoardControl" ItemsSource="{DynamicResource CellCollectionData}">
</ItemsControl>

所以我的问题是:

  1. 如何将我的数组 [,] 放入 ItemsControl?我不知道如何进行 DataBind,我已经阅读了一些教程,但我才刚刚开始了解 DataContext 是什么。
  2. 如何在每回合后刷新棋盘?我不想在点击 btnNext_Turn 之前更新板子。
  3. 用户在 UI 中输入新词并单击 btn_Next_Turn 后如何更新 ModelBoard?

我是编码初学者,这是我在 C# 和 WPF 中的第一个真正项目。我的老师既不懂 WPF 也不懂 C#,所以 StackoverFlow 社区的各位在过去几周里给了我很大的帮助,尤其是在 SQL 方面帮助我。

请,任何帮助将不胜感激!

更新:

感谢 Erno 的快速回复!是的,我收到了 EventHandler 的错误,所以我将它换成了 PropertyChangedEventHandler 并停止了错误。

public partial class GameBoard : UserControl
{
    TileCollection RefreshTiles = new TileCollection();
    public GameBoard()
    {
        InitializeComponent();
    }
    public void getBoard()
    {
        string[,] ArrayToAdd = InnerModel.ModelBoard;
        for (int i = 0; i < 15; i++)
        {
            for (int j = 0; j < 15; j++)
            {
                Tile AddTile = new Tile();
                AddTile.Charater = ArrayToAdd[i, j];
                AddTile.X = i;
                AddTile.Y = j;
                RefreshTiles.Add(AddTile);
            }
        }
    }
}

所以当我运行调试时,我可以看到 RefreshTiles 集合被填充。但是,如何将 Collection 绑定到 ItemsControl?

我是否将 UserControl 的 DataContext 设置为 RefreshTiles?

this.DataContext = RefreshTiles

然后在 XAML 中

<ItemsControl Name ="BoardControl" ItemsSource="{Binding}">

更新:

显然,如果我在 MainWindow 中设置绑定,它可以完美运行,但是当我尝试从用户控件绑定时这不起作用?我在“RefreshArray”中设置了一个断点,我可以看到它被填充了,但是 UserControl 没有更新?

public partial class UserControl1 : UserControl
{
    public char GetIteration
    {
        get { return MainWindow.Iteration; }
        set { MainWindow.Iteration = value; }
    }
    CellCollection NewCells = new CellCollection();
    public UserControl1()
    {
        InitializeComponent();
        this.DataContext = NewCells;
        PopulateCells();
    }
    private void PopulateCells()
    {
        for (int i = 0; i < 15; i++)
        {
            for (int j = 0; j < 15; j++)
            {
                Cell NewCell = new Cell();
                NewCell.Character = "A";
                NewCell.Pos_x = i;
                NewCell.Pos_y = j;
                NewCells.Add(NewCell);
            }
        }
    }
    public void RefreshArray()
    {
        NewCells.Clear();
        for (int i = 0; i < 15; i++)
        {
            for (int j = 0; j < 15; j++)
            {
                Cell ReCell = new Cell();
                ReCell.Character = GetIteration.ToString();
                ReCell.Pos_x = i;
                ReCell.Pos_y = j;
                NewCells.Add(ReCell);
            }
        }
        this.DataContext = NewCells;
    }
}

public partial class MainWindow : Window
{
    UserControl1 Control = new UserControl1();
    public static char Iteration = new char();
    public MainWindow()
    {
        InitializeComponent();
    }


    private void Next_Click(object sender, RoutedEventArgs e)
    {
        Iteration = 'B';
        Control.RefreshArray();
    }
}

这不起作用,而下面的那个起作用

public partial class MainWindow : Window
{
    char Iteration = new char();
    CellCollection NewCells = new CellCollection();
    public MainWindow()
    {
        InitializeComponent();
        PopulateCells();
        this.DataContext = NewCells;
        Iteration++;
    }
    private void PopulateCells()
    {
        for (int i = 0; i < 15; i++)
        {
            for (int j = 0; j < 15; j++)
            {
                Cell NewCell = new Cell();
                NewCell.Character = "A";
                NewCell.Pos_x = i;
                NewCell.Pos_y = j;
                NewCells.Add(NewCell);
            }
        }
    }
    private void RefreshArray()
    {
        NewCells.Clear();
        for (int i = 0; i < 15; i++)
        {
            for (int j = 0; j < 15; j++)
            {
                Cell ReCell = new Cell();
                ReCell.Character = Iteration;
                ReCell.Pos_x = i;
                ReCell.Pos_y = j;
                NewCells.Add(ReCell);
            }
        }
    }

    private void Next_Click(object sender, RoutedEventArgs e)
    {
        RefreshArray();
    }
}

【问题讨论】:

    标签: c# wpf xaml


    【解决方案1】:

    这个问题没有简短的答案,所以我会给出一个大纲,随时提出更多问题,以便在需要时获得更多详细信息:

    要使用数据绑定,请确保集合中的项目实现 INotifyPropertyChanged。目前您正在使用一个字符串数组。字符串不实现 INotifyPropertyChanged。

    还要确保集合实现了 INotifyCollectionChanged。目前您正在使用二维数组。数组没有实现这个接口。

    一种解决方案是创建一个名为 Tile 的类,该类实现 INotifyPropertyChanged 并将字符存储为字符串,并将其在棋盘上的位置存储在 X 和 Y 属性中:

    public class Tile : INotifyPropertyChanged
    {
        private string character;
        public string Character
        {
            get
            {
                return character;
            }
            set
            {
                if(character != value)
                {
                    character = value;
                    OnPropertyChanged("Character");
                }
            }
        }
    
        private int x; // repeat for y and Y
        public int X
        {
            get
            {
                return x;
            }
            set
            {
                if(x != value)
                {
                    x = value;
                    OnPropertyChanged("X");
                }
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged(string propertyName)
        {
            var p = PropertyChanged;
            if(p != null)
            {
                p(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    

    像这样创建一个类 Tiles:

    public class Tiles : ObservableCollection<Tile>
    {
    }
    

    并用这个集合替换二维数组。

    将项目控件绑定到集合的一种方法要求您将项目控件的 DataContext 设置为集合的实例并指定属性 ItemsSource="{Binding}"

    使用 itemtemplate 中的 Character、X 和 Y 属性来显示文本并定位图块。

    这样,当您在添加或删除图块时操作 Tiles 集合时,绑定将自动更新视图,并且当图块更改(位置或内容)时,板也会更新

    【讨论】:

    • @Tony Wu:请注意,我没有使用编译器检查我的代码可能有错误。
    • @Tony Wu,是的,您的代码看起来不错。对不起,我会在我的回答中解决这个错误。您建议的绑定很好。无需将整个用户控件绑定到 Tiles,您只需将 ItemsControl 绑定到 Tile。另外:您现在可以完全跳过数组。
    • 嗯,我应该为 DataTemplate 设置什么?我之前有一个带有边框的修改过的 TextBlock,但是这似乎不适用于新的 tile 对象
    • 不幸的是它仍然无法正常工作 :( 我看到 Collection 已被填满,但 UI 上仍然没有任何内容。我一定是做错了什么。
    • 如果集合被填满,可能是ItemTemplate和Tile不匹配。您在输出中看到任何绑定错误吗?问题中的代码是最新的吗? (我可能有时间看看)
    【解决方案2】:

    Emo 的方法很好;我建议采用稍微不同的策略。

    首先,将现有程序搁置一旁。你稍后会回来。

    接下来,实现一个原型 WPF 项目。在此项目中,创建一个公开 LetterRowColumn 属性的类,以及公开这些对象集合的另一个类。编写一个用测试数据填充此集合的方法。

    在您的主窗口中,实现一个ItemsControl 来展示这个集合。这个控件需要四个东西:

    1. 它的ItemsPanel 必须包含一个面板模板,用于排列它所包含的项目。在这种情况下,您将使用具有预定义大小的行和列的 Grid

    2. 它的ItemContainerStyle 必须包含设置器,告诉ContentPresenter 对象模板生成它们属于网格的哪一行和哪一列。

    3. 它的ItemTemplate 必须包含一个模板,告诉它应该在ContentPresenters 中放置哪些控件。

    4. 它的ItemsSource 必须绑定到对象集合。

    最小版本如下所示:

    <ItemsControl ItemsSource="{Binding}">
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
          <Grid>
            <Grid.RowDefinitions>
              <RowDefinition Height="10"/>
              <RowDefinition Height="10"/>
              <RowDefinition Height="10"/>
              <RowDefinition Height="10"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="10"/>
              <ColumnDefinition Width="10"/>
              <ColumnDefinition Width="10"/>
              <ColumnDefinition Width="10"/>
            </Grid.ColumnDefinitions>
          </Grid>      
        </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
      <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
          <Setter Property="Grid.Row" Value="{Binding Row}"/>
          <Setter Property="Grid.Column" Value="{Binding Column}"/>
        </Style>
      </ItemsControl.ItemContainerStyle>
      <ItemsControl.ItemTemplate>
        <DataTemplate TargetType="{x:Type MyClass}">
          <TextBlock Text="{Binding Letter}"/>
        </DataTemplate>
      </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    将主窗口的DataContext 设置为您的集合的填充实例,您应该会看到它以 4x4 网格排列字母。

    这是如何工作的:通过将ItemsSource 设置为{Binding},您是在告诉ItemsControlDataContext 获取其项目。 (控件从其父级继承其DataContext,因此将其设置在窗口上可使其对ItemsControl 可用。

    当 WPF 呈现 ItemsControl 时,它会使用 ItemsPanelTemplate 创建一个面板并用项目填充它。为此,它会遍历ItemsSource 中的项目,并为每个项目生成一个ContentPresenter 控件。

    它使用ItemTemplate 属性中的模板填充该控件的Content 属性。

    它使用ItemContainerStyle 属性中的样式设置ContentPresenter 的属性。在这种情况下,样式设置了 Grid.RowGrid.Column 附加属性,它们告诉 Grid 在将它们绘制到屏幕上时将它们放在哪里。

    所以创建的实际对象如下所示:

    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="10"/>
        <RowDefinition Height="10"/>
        <RowDefinition Height="10"/>
        <RowDefinition Height="10"/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="10"/>
        <ColumnDefinition Width="10"/>
        <ColumnDefinition Width="10"/>
        <ColumnDefinition Width="10"/>
      </Grid.ColumnDefinitions>
      <ContentPresenter Grid.Row="0" Grid.Column="0">
        <ContentPresenter.Content>
          <TextBlock Text="A"/>
        </ContentPresenter.Content>
      </ContentPresenter>
      <ContentPresenter Grid.Row="1" Grid.Column="1">
        <ContentPresenter.Content>
          <TextBlock Text="A"/>
        </ContentPresenter.Content>
      </ContentPresenter>
    </Grid>      
    

    (没有创建实际的 XAML,但上面的 XAML 很好地表示了创建的对象。)

    一旦你有了这个工作,你现在有一堆相对简单的问题要解决:

    1. 您如何使它看起来更像您想要的样子?这个问题的答案将涉及制作一个更详细的ItemTemplate

    2. 如何将当前呈现的集合与应用程序中的数组同步?这(很大程度上)取决于您的应用程序是如何设计的;一种方法是将集合包装在一个类中,让该类创建后端对象模型的实例,并让后端对象模型在其集合每次更改时引发一个事件,以便包含对象集合的类在 UI 中呈现的对象知道创建一个新的前端对象并将其添加到其集合中。

    3. 用户如何在网格中选择一个单元格来放入一个磁贴?这个问题的答案可能涉及为所有单元格创建对象,而不仅仅是包含字母的单元格,实现一个命令以在用户单击单元格时执行,并更改@987654353 @ 以便在用户点击时执行该命令。

    4. 如果单元格的内容发生变化,UI 如何发现它?这将需要在您的 UI 对象类中实现 INotifyPropertyChanged,并在 Letter 更改时提升 PropertyChanged

    如果您没有注意到,我在这里推荐的方法最重要的一点是,您实际上可以让 UI 几乎完全独立于您在后端执行的操作而工作。 UI 和后端仅在您创建的第一个类中耦合在一起,该类具有 RowColumnLetter 属性。

    顺便说一下,这是模型/视图/视图模型模式的一个很好的例子,如果您对 WPF 感兴趣,您可能听说过。您编写的代码就是模型。窗户就是风景。而具有RowColumnLetter 属性的类就是视图模型。

    【讨论】:

    • 您好,感谢您的回复。我不能在 DataTemplate 中有一个名为 TargetType 的属性,你的意思是 DataType 吗?在这种情况下,MyClass 是什么?细胞集合?再次感谢
    • 我想知道为什么 Kaxaml 没有标记这一点。但是,是的,应该是DataType。实际上,在这种情况下,您甚至不需要指定目标DataType,因为使用该属性的唯一原因是在您呈现不同类型的对象时为 WPF 提供一种从多个模板中进行选择的方法。无论如何,这个上下文中的MyClass 数据类型是单元类;模板定义了每个单元格的表示。
    猜你喜欢
    • 1970-01-01
    • 2019-10-22
    • 2011-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多