如何从视图模型在 Windows Phone 8.1 中的 canvas 内生成动态按钮?

How to generate dynamic buttons inside a canvas in Windows Phone 8.1 from viewmodel?

编辑:因为我得到的建议太多了,所以首先我考虑逐步更改它,所以我的第一个问题是将动态按钮从 ViewModel 获取到 View。

我正在编写 WP 8.1 游戏应用程序。所以从 MainPage.xaml 我的游戏导航到 GamePage.xaml。在 OnNavigatedTo 方法中,我有这样的代码。

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    // Initialize GamePageViewModel
    gmPageViewModel = new GamePageViewModel(this);
    this.DataContext = gmPageViewModel;
}

首先在视图中我为网格设置了 DataContext 我将 DataContext 设置为

<Grid 
   DataContext="{Binding GamePageVM, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<StackPanel Orientation="Horizontal" Grid.Row="1" >
     <TextBlock Text="{Binding Value, Mode=TwoWay}"/>
     <TextBlock Text="{Binding Moves, Mode=TwoWay}" />
</StackPanel>
<Canvas x:Name="GameCanvas" Grid.Row="2" 
                DataContext="{Binding ButtonItems, 
                              UpdateSourceTrigger=PropertyChanged, 
                              Mode=TwoWay}">
<ItemsControl >
    <ItemsControl.ItemsPanel>
       <ItemsPanelTemplate>
          <Canvas />
       </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
     <Style TargetType="ContentPresenter">
        <Setter Property="Canvas.Left" Value="{Binding TopLeftX}"/>
        <Setter Property="Canvas.Top" Value="{Binding TopLeftY}"/>
     </Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
    <DataTemplate>
        <Button Width="{Binding Width}" 
         Height="{Binding Height}" 
         Command="{Binding ButtonClickCommand}"
         CommandParameter="{Binding Content, 
         RelativeSource={RelativeSource Self}}"/>
    </DataTemplate>
</ItemsControl.ItemTemplate>
</Grid>

这是我的视图,现在我根据某些游戏标签生成一些随机按钮,可以是 5 个也可以是 15 个,但条件是按钮不能相互重叠。 因此,在 ViewModel 中,我有一个名为 ButtonItems 的 属性 绑定到 Canvas DataContext、"Value" 和 "Moves" 绑定到两个 TextBlock,如代码。

视图模型:

public ObservableCollection<ButtonPosition> ButtonItems{...}
public string Moves{...}
public string Value {...}
public GamePageViewModel GamePageViewModels{...}

public GamePageViewModel()
        {
            initTheBoard();
            _moves= GameObject.Moves.ToString();
            _value = GameObject.Value.ToString();
        }

GameObject 是一个生成一些唯一随机数的对象。

现在我创建动态按钮的 InItBoard() ButtonItems

    private void initTheBoard()
    {
      buttonItems = new ObservableCollection<ButtonPosition>();
      Random r = new Random();
      List<ButtonPosition> buttonPositions = new List<ButtonPosition>();
      Button button;

      for (int i = 0; i < numOfButtons; i++)
      {
         button = new Button();
         bool foundOverlap = false;
         int left;
         int top;
         ButtonPosition bp;
         do
         {
            foundOverlap = false;
            // Create a new random position for the button (subtracting the width/height 
            // from the X,Y so that we don't overlap the edge of the canvas)
         left = r.Next(0, 500 - buttonWidth - 10);
         top = r.Next(0, 500 - buttonHeight - 10);
         bp = new ButtonPosition(left, top, buttonWidth, buttonHeight);
         // Check each of the existing buttons for overlap
         foreach (ButtonPosition existingButton in buttonPositions)
         {
            if (bp.Overlaps(existingButton))
            {
               foundOverlap = true;
               break;
            }
         }
        } while (foundOverlap);

    buttonItems.Add(bp);
    buttonPositions.Add(bp);
   }
 }

 gamePageVM = this;

因此,我以这种方式填充了 viewModel,但 View 不显示任何动态创建的按钮。我发现在 Windows Phone 8.1 <ItemsControl.ItemContainerStyle> 中没有给出任何结果,所以我将其删除并放入 Canvas.Left="{Binding TopLeftX}" Canvas.Top="{Binding TopLeftY}"<DataTemplate> 但没有结果。

ButtonsPositions 还有一件事 class 它具有检查任何按钮是否与任何其他按钮重叠的逻辑,它有两个 属性 TopLeftX、TopLeftY,它们正在竞标XAML。

public class ButtonsPositions
 {
    public int TopLeftX { get; set; }
    public int TopLeftY { get; set; }
    public int Width {get; set;}
    public int Height{get; set;}
 }

此高度、宽度、TopLeftX 和TopLeftY 绑定到按钮的Datatemplate。 任何帮助,为什么按钮没有加载到 canvas。

如果您只想替换视图模型中的 GamePage 对象,请向您的视图模型添加一个接受 GamePage.

的方法
public void ReplaceGamePage(GamePage page)
{
     this.GamePage = page;
}

如果视图模型上的游戏页面 属性 连接了 属性 更改事件,您的视图将被刷新。另一种方法是强制刷新视图上的数据上下文。在您的代码隐藏点击事件处理程序中,您可以引发 on 属性 changed 事件。

this.OnPropertyChanged(new DependencyPropertyChangedEventArgs(this.DataContext, this.DataContext, new GamePage()));

这将刷新 DataContext 绑定,强制视图更新其与视图模型的所有绑定。

不过,我强烈建议您停止将特定于视图的内容混合到您的视图模型中。 MVVM 是一条单行道。视图模型不应该知道视图元素,只提供视图绑定的数据。

您应该做的是将按钮隐藏在模型后面,给每个模型一个随机的 X/Y 位置,然后将模型集合绑定到视图上的 ItemsControl。您可以为模型分配一个 DataTemplate,将每个模型呈现为一个按钮,位于模型中定义的 X/Y 值处。这将使您可以将命令连接到您的视图模型并消除对代码隐藏的需要。

经过很多时间,尝试了很多选项后,我终于找到了问题所在,让我告诉你问题出在哪里...

在 WPF 中

   <ItemsControl.ItemContainerStyle>
      <Style TargetType="ContentPresenter">
         <Setter Property="Canvas.Left" Value="{Binding Path=TopLeftX}" />       
         <Setter Property="Canvas.Top"  Value="{Binding Path=TopLeftY}" />
      </Style>
   </ItemsControl.ItemContainerStyle>

这非常有效,但是对于 WPF,n silverlight,在 Windows Phone 8.1 或 WinRT 中这不起作用,如果您设置 Value="50" - 一些固定值它确实有效,但任何 DataBinding 都会将值设置为 0,0 并将项目放在 canvas 中的第 0,0 个位置。

所以在 WinRT 中它不能那样工作,经过大量研究和搜索我找到了解决方案。它位于 DataTemplate 如果我们使用

<Button.RenderTransform>
    <TransformGroup>
       <TranslateTransform X="{Binding TopLeftX}" Y="{Binding TopLeftY}" />
       </TransformGroup>
</Button.RenderTransform>

我们还需要进行一项更改。

<ItemsControl.ItemContainerStyle>
    <Style TargetType="FrameworkElement">
      <Setter Property="Canvas.Left" Value="{Binding Path=TopLeftX}" />
      <Setter Property="Canvas.Top"  Value="{Binding Path=TopLeftY}" />
    </Style>
</ItemsControl.ItemContainerStyle>

我们必须为 Silverlight

使用 FrameworkElement 而不是 ContentPresenter

这就是我想要的解决方案。它就像一个魅力。