如何绑定一个简单的数组?

时间:2011-07-25 09:33:33

标签: c# wpf xaml

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

截至目前,我已获得以下XAML代码:

我的“MainWindow”包含:

按钮:

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

UserControl:哪个是游戏板(GameBoard也是另一个UserControl)和玩家的架子

<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>

UI Structure

所以我的问题是:

  1. 如何将我的数组[,]添加到ItemsControl中?我不知道如何使用DataBind,我已经阅读了几个教程,但我只是开始得到DataContext的内容。
  2. 每次转弯后如何刷新电路板?我不希望在点击btnNext_Turn之前更新电路板。
  3. 如何在用户将新单词输入UI并单击btn_Next_Turn后更新ModelBoard?
  4. 我是编码的初学者,这是我在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中设置绑定它可以正常工作但是当我尝试从Usercontrol进行绑定时这不起作用?我在“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();
        }
    }
    

2 个答案:

答案 0 :(得分:3)

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

要使用数据绑定,请确保集合中的项目实现INotifyPropertyChanged。目前您正在使用字符串数组。 String不实现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>
{
}

并用此集合替换二维数组。

将项控件绑定到集合的一种方法是,您需要将items控件的DataContext设置为集合的实例并指定属性ItemsSource =“{Binding}”

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

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

答案 1 :(得分:2)

Emo的方法很好;我建议稍微改变一下。

首先,将现有程序放在一边。你稍后会回来的。

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

在主窗口中,实现ItemsControl以显示此集合。这种控制需要四件事:

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

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

  3. ItemTemplate必须包含一个模板,告诉它应该在ContentPresenter中添加哪些控件。

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

  5. 最小版本如下所示:

    <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控件。

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

    它使用ContentPresenter属性中的样式设置ItemContainerStyle上的属性。在这个例子中,样式设置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. 用户如何选择网格中的单元格以放置图块?对此的答案可能涉及为所有单元创建对象,而不仅仅是包含字母的对象,实现在用户单击单元格时执行的命令,以及更改{{ 1}}以便当用户点击它时它可以执行此命令。

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

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

      顺便说一下,如果您对WPF感兴趣,这可能是您可能听说过的Model / View / ViewModel模式的一个很好的例子。你写的代码是模型。窗口是视图。具有LetterRowColumn属性的类是视图模型。