如何从视图模型在 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
这就是我想要的解决方案。它就像一个魅力。
编辑:因为我得到的建议太多了,所以首先我考虑逐步更改它,所以我的第一个问题是将动态按钮从 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
这就是我想要的解决方案。它就像一个魅力。