【问题标题】:How to generate dynamic buttons inside a canvas in Windows Phone 8.1 from viewmodel?如何从 viewmodel 在 Windows Phone 8.1 的画布内生成动态按钮?
【发布时间】:2015-05-19 18:04:55
【问题描述】:

编辑:因为我得到的建议太多,所以我首先考虑逐步更改它,所以我的第一个问题是将动态按钮从 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 &lt;ItemsControl.ItemContainerStyle&gt; 中没有给出任何结果,所以我将其删除并放入 Canvas.Left="{Binding TopLeftX}" Canvas.Top="{Binding TopLeftY}"&lt;DataTemplate&gt; 但没有结果。

ButtonsPositions 是另外一个类,它具有检查任何按钮是否与任何其他按钮重叠的逻辑,它有两个属性 TopLeftX,TopLeftY,即 Biding to XAML。

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

这个Height、Width、TopLeftX 和TopLeftY 绑定到按钮的Datatemplate。 任何帮助,为什么按钮没有加载到画布中。

【问题讨论】:

    标签: c# wpf xaml data-binding win-universal-app


    【解决方案1】:

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

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

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

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

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

    不过,我强烈建议您停止将视图特定的内容混入您的视图模型中。 MVVM 是一条单向的道路。视图模型不应该知道视图元素,只为要绑定的视图呈现数据。

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

    【讨论】:

    • ReplaceGamePage 将在 viewModel 中并接受参数 GamePage,但在我的 ViewModel 中没有 gamepage 的属性,并且 click_event 处理程序不在 viewModel 中的代码中. DependencyPropertyChangedEventArgs(this.DataContext, this.DataContext, new GamePage())) 给出错误,它的构造函数不包含三个参数
    • 感谢按钮 - 模型建议会尝试,问题是我根据游戏标签随机选择的按钮编号。因此面临困难,主要关注点是动态刷新游戏页面并在画布中添加一些动画以及当用户单击按钮时
    • 您根本不应该将您的视图传递给您的视图模型。 ViewModel 不应该知道视图实例存在。
    • 如果你使用 MVVM 模式,你可以在不给视图模型你的视图引用的情况下实现这一点。视图模型仍然可以根据标签数据绑定到的 字符串属性 为模型分配随机数。在您的视图模型中没有 Label 引用。给视图添加标签,绑定到视图模型上的字符串属性,让视图模型根据字符串属性为模型生成编号。然后,您的视图可以通过模板从模型集合中创建按钮,如我的回答中所述。
    • 我已经更新了我的问题,我想如果我可以按照您的建议从 ViewModel 填充视图,我可以获得刷新 UI 功能,但我仍然无法让它工作,我已经发布整个代码可能。你能找出错误是什么吗?
    【解决方案2】:

    经过很多时间和尝试了很多选项后,我终于找到了问题,让我告诉你问题是什么......

    在 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 并将项目放在画布中的 0,0 th 位置。

    所以在 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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-14
      • 2016-05-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多