RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 573050
Accepted
Saint
Saint
Asked:2020-10-03 15:47:40 +0000 UTC2020-10-03 15:47:40 +0000 UTC 2020-10-03 15:47:40 +0000 UTC

WPF游戏,将WinForms代码移植到MVVM

  • 772

我想实现:按钮排列成正方形(作为二维 NxN 数组)。单击按钮时,将旋转一行和一列中的所有按钮。数字 N 是可定制的。一开始我什么都做MainWindow.xaml.cs,他们建议我正常做一切并使用它MVVM。代码MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private Button[,] CreateButtons(int quantity)
    {
        Form.Rows = quantity;
        Form.Columns = quantity;
        Button[,] buttons = new Button[quantity, quantity];
        for (int i = 0; i < quantity; i++)
        {
            for (int j = 0; j < quantity; j++)
            {
                buttons[i, j] = new Button();
                buttons[i, j].Width = 100;
                buttons[i, j].Height = 20;
                buttons[i, j].Margin = new Thickness(5,80,0,0);
                buttons[i, j].Click += new RoutedEventHandler(new_button_click);
            }
        }
        return buttons;
    }

    void new_button_click(object sender, RoutedEventArgs e)
    {
        Button btn = sender as Button;
        if (btn != null)
        {
            var rotateTransform = btn.RenderTransform as RotateTransform;
            var transform = new RotateTransform(90 + (rotateTransform == null ? 0 : rotateTransform.Angle));
            transform.CenterX = 50;
            transform.CenterY = 10;
            btn.RenderTransform = transform;
        }
    }

    private void AddToWrapPanel(int quantity, Button[,] buttons)
    {
        for (int i = 0; i < quantity; i++)
            for (int j = 0; j < quantity; j++)
            {
                Form.Children.Add(buttons[i, j]);
            }
    }

    private int GetQuantityButtons()
    {
        ComboBoxItem item = (ComboBoxItem)comboBox1.SelectedItem;
        int count = int.Parse((string)item.Content);
        return count;
    }

    private void СreateButton_Click(object sender, RoutedEventArgs e)
    {
        if (Form.Children.Count > 0)
            Form.Children.Clear();
        int count = GetQuantityButtons();
        Button[,] buttons = CreateButtons(count);
        AddToWrapPanel(count, buttons);
    }
}

现在我开始移动所有东西。

XAML:

<Window x:Class="Di.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:user="clr-namespace:Di"
    Title="Сейф" Height="715.6" Width="840" Left="250" Top="10" Background="Silver" ResizeMode="CanMinimize"
    TextOptions.TextFormattingMode="Display" Icon="Resources/Icon1.ico">
<Window.DataContext>
    <user:MainWindowModel />
</Window.DataContext>
<Grid>
    <ItemsControl Margin="0,30,0,0">
        <UniformGrid x:Name="Form"/>
        <WrapPanel Name="wrapPanel" Background="#FFF2F2F2" />
    </ItemsControl>
    <Button Content="Старт" Height="23" HorizontalAlignment="Left" Margin="457,12,0,0" Name="createButton" VerticalAlignment="Top" Width="75" Click="СreateButton_Click" Command="{Binding Seter}" />
    <ComboBox Height="23" HorizontalAlignment="Left" Margin="368,12,0,0" Name="comboBox1" VerticalAlignment="Top" Width="74" SelectedIndex="0" RenderTransformOrigin="0.5,0.739">
        <ComboBoxItem Content="{Binding Three}" />
        <ComboBoxItem Content="{Binding Four}" />
        <ComboBoxItem Content="{Binding Five}" />
        <ComboBoxItem Content="{Binding Six}" />
    </ComboBox>
    <Label Content="{Binding Lvl}" HorizontalAlignment="Left" Margin="254,10,0,0" VerticalAlignment="Top"/>
</Grid>

代码VM:

public class MainWindowModel
{
    public int Three { get; set; }
    public int Four { get; set; }
    public int Five { get; set; }
    public int Six { get; set; }
    public string Lvl { get; set; }

    public MainWindowModel()
    {
        Three = 3;
        Four = 4;
        Five = 5;
        Six = 6;
        Lvl = "Сложность (3 - 6):";
    }

    private ICommand _seter;

    public ICommand Seter
    {
        get
        {
            return _seter ?? (_seter = new RelayCommand(() =>
              {
                  // действие при вызове команды
              }));
        }

    }

}

到目前为止,只有这样.. 请帮助我转移并完成我计划的时刻。例如:如何VM在服务按钮中创建一个属性RotationAngle?单击“创建”然后使用它们时如何生成一组按钮?如何访问UniformGrid行数和列数并将其与所选中int的关联combobox?

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    VladD
    2020-10-03T21:21:07Z2020-10-03T21:21:07Z

    我们先走吧。让我们构建一个虚拟机。我们将需要一个 VM 的基类,其中将有一个实现INotifyPropertyChanged:

    class VM : INotifyPropertyChanged
    {
        protected bool Set<T>(ref T field, T value,
                             [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value))
                return false;
    
            field = value;
            RaisePropertyChanged(propertyName);
            return true;
        }
    
        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    (如果您正在使用某种 MVVM 框架,那么您可能已经定义了一个类似的基类。)

    现在是一个单元格的 VM。我们需要知道什么?旋转角度 - 让我们用 INPC 从中创建一个属性。行和列 - 这些属性是不可变的。以及激活单元格时将调用的命令。(她也是一成不变的。)

    单击单元格时将执行的操作不能由单元格本身执行,因为许多单元格会旋转。因此,我们会将反应作为参数传递给“来自上方”的动作。我们得到这段代码:

    class CellVM : VM
    {
        public CellVM(int row, int column, Action<int, int> onActivate)
        {
            Row = row;
            Column = column;
            Activate = new RelayCommand(() => onActivate(row, column));
        }
    
        double rotationAngle = 0;
        public double RotationAngle
        {
            get { return rotationAngle; }
            set { Set(ref rotationAngle, value); }
        }
    
        public int Row { get; }
        public int Column { get; }
    
        public ICommand Activate { get; }
    }
    

    下一个 VM 是整个电路板。她将不得不对细胞的轮换做出决定。我们需要什么数据?需要宽度和高度,更改时需要重新创建单元格数组。我们需要细胞本身,因为细胞只会作为一个整体被替换,所以我们不取ObservableCollection<CellVM>,而是简单地取IEnumerable<CellVM>。方阵是不可能暴露在外面的,谁也不知道怎么附和上去。因此,我们将所有“合并”的单元格公开到一个公共序列中。

    class BoardVM : VM
    {
        int width;
        public int Width
        {
            get { return width; }
            set { if (Set(ref width, value)) { GenerateCells(); } }
        }
    
        int height;
        public int Height
        {
            get { return height; }
            set { if (Set(ref height, value)) { GenerateCells(); } }
        }
    
        CellVM[,] cells;
    
        public IEnumerable<CellVM> AllCells => cells.Cast<CellVM>();
    

    接下来,当更改行数或列数时,我们需要重新生成单元格。

        void GenerateCells()
        {
            var cells = new CellVM[width, height];
            for (int row = 0; row < height; row++)
                for (int column = 0; column < width; column++)
                    cells[column, row] = new CellVM(row, column, OnCellActivate);
            ShuffleAngles(cells);
            // отбрасываем существующие клетки
            this.cells = cells;
            RaisePropertyChanged(nameof(AllCells));
        }
    

    ...并将其设置为随机起始角度:

        static Random random = new Random();
        void ShuffleAngles(CellVM[,] cells)
        {
            for (int y = 0; y < height; y++)
                for (int x = 0; x < width; x++)
                    cells[x, y].RotationAngle = random.Next(4) * 90;
        }
    

    现在是激活单元格时调用的函数。我们需要旋转同一列和同一行中的所有单元格。在第二个循环中,我们跳过已经旋转的单元格。

        void OnCellActivate(int row0, int column0)
        {
            for (int row = 0; row < height; row++)
                Rotate(cells[column0, row]);
    
            for (int column = 0; column < width; column++)
                if (column != column0)
                    Rotate(cells[column, row0]);
        }
    
        void Rotate(CellVM cellVM)
        {
            cellVM.RotationAngle = (cellVM.RotationAngle + 90) % 360;
        }
    }
    

    好的,VM 或多或少是清楚的。让我们继续查看。

    我们需要ItemsControl,因为我们想要显示一系列元素。我们有一个包含在属性中的元素序列AllCells。

    <ItemsControl ItemsSource="{Binding AllCells}">
    

    接下来,我们需要将单元格放入UniformGrid. 我们选择作为载体UniformGrid,同时绑定行数和列数:

        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid IsItemsHost="True"
                             Rows="{Binding Width}" Columns="{Binding Height}"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    

    此外,如何显示单独的单元格?你要Button,就给。我们写DataTemplate。

        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="{x:Type vm:CellVM}">
                <Button Command="{Binding Activate}"/>
    

    运行程序,我们看到按钮太靠近单元格的边界,所以我们给它Margin="10"。现在,我们需要以某种方式指出顶部和底部的位置。为此,绘制一个向上箭头(但您必须做一些更漂亮的事情)。我们将从绑定角度旋转箭头:

        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="{x:Type vm:CellVM}">
                <Button Command="{Binding Activate}" Margin="10" Padding="10">
                    <Path Data="M 0,1 L 1,0 L 2,1 M 1,2 L 1,0"
                          Stroke="Black" Stretch="Uniform" RenderTransformOrigin="0.5,0.5">
                        <Path.RenderTransform>
                            <RotateTransform Angle="{Binding RotationAngle}"/>
                        </Path.RenderTransform>
                    </Path>
                </Button>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    

    似乎就是一切。

    </ItemsControl>
    

    现在我们需要设置字段的大小。

    要以一种好的方式做到这一点,您需要启动另一个窗口(并且只在游戏开始时显示它),因为在游戏过程中改变场地的大小在某种程度上是错误的。但是在我们的快速原型中,我们会对此视而不见。(然后你必须重做。)

    因此,我们需要有关我们有多少行和多少列的信息。我们回到虚拟机并开始上课:

    static class GameInfo
    {
        static public IEnumerable<int> PossibleColumnNumber { get; } = new[] { 3, 4, 5, 6 };
        static public IEnumerable<int> PossibleRowNumber { get; } = new[] { 3, 4, 5, 6 };
    }
    

    为了美观,我们需要将值初始化为BoardVM有效数字。查找和更改行:

        int width = GameInfo.PossibleColumnNumber.Min();
    

    和

        int height = GameInfo.PossibleRowNumber.Min();
    

    现在查看。向它们添加两个组合框和标签:

    <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Center">
        <Label Target="{Binding ElementName=ColumnChooser}">Columns: </Label>
        <ComboBox Name="ColumnChooser" SelectedItem="{Binding Width}"
                  ItemsSource="{x:Static vm:GameInfo.PossibleColumnNumber}"/>
        <Label Target="{Binding ElementName=RowChooser}">Rows:</Label>
        <ComboBox Name="RowChooser" SelectedItem="{Binding Height}"
                  ItemsSource="{x:Static vm:GameInfo.PossibleRowNumber}"/>
    </StackPanel>
    

    整体MainWindow.xaml:

    <Window x:Class="View.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:vm="clr-namespace:ViewModels"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            mc:Ignorable="d" 
            Title="Test" Height="350" Width="350">
        <Grid d:DataContext="{d:DesignInstance Type=vm:BoardVM, IsDesignTimeCreatable=False}">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <ItemsControl ItemsSource="{Binding AllCells}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid IsItemsHost="True"
                                     Rows="{Binding Width}" Columns="{Binding Height}"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate DataType="{x:Type vm:CellVM}">
                        <Button Command="{Binding Activate}" Margin="10" Padding="10">
                            <Path Data="M 0,1 L 1,0 L 2,1 M 1,2 L 1,0"
                                  Stroke="Black" Stretch="Uniform"
                                  RenderTransformOrigin="0.5,0.5">
                                <Path.RenderTransform>
                                    <RotateTransform Angle="{Binding RotationAngle}"/>
                                </Path.RenderTransform>
                            </Path>
                        </Button>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
            <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Center">
                <Label Target="{Binding ElementName=ColumnChooser}">Columns: </Label>
                <ComboBox Name="ColumnChooser" SelectedItem="{Binding Width}"
                          ItemsSource="{x:Static vm:GameInfo.PossibleColumnNumber}"/>
                <Label Target="{Binding ElementName=RowChooser}">Rows:</Label>
                <ComboBox Name="RowChooser" SelectedItem="{Binding Height}"
                          ItemsSource="{x:Static vm:GameInfo.PossibleRowNumber}"/>
            </StackPanel>
        </Grid>
    </Window>
    

    现在我们需要将 VM 附加到视图。最好不要在 XAML 中,而是在App.xaml.cs(参见此处)中执行此操作。我们写:

    public partial class App : Application
    {
        BoardVM boardVM = new BoardVM();
    
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            new MainWindow() { DataContext = boardVM }.Show();
        }
    }
    

    并从 App.xaml 中删除StartupUri。

    Компилируем, запускаем. Сразу видим пустое поле. Недоработка, мы ж не сгенерировали поле в конструкторе BoardVM! Исправляем:

        public BoardVM()
        {
            GenerateCells();
        }
    

    Вот что у меня получилось:

    анимашка

    • 11

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何停止编写糟糕的代码?

    • 3 个回答
  • Marko Smith

    onCreateView 方法重构

    • 1 个回答
  • Marko Smith

    通用还是非通用

    • 2 个回答
  • Marko Smith

    如何访问 jQuery 中的列

    • 1 个回答
  • Marko Smith

    *.tga 文件的组重命名(3620 个)

    • 1 个回答
  • Marko Smith

    内存分配列表C#

    • 1 个回答
  • Marko Smith

    常规赛适度贪婪

    • 1 个回答
  • Marko Smith

    如何制作自己的自动完成/自动更正?

    • 1 个回答
  • Marko Smith

    选择斐波那契数列

    • 2 个回答
  • Marko Smith

    所有 API 版本中的通用权限代码

    • 2 个回答
  • Martin Hope
    jfs *(星号)和 ** 双星号在 Python 中是什么意思? 2020-11-23 05:07:40 +0000 UTC
  • Martin Hope
    hwak 哪个孩子调用了父母的静态方法?还是不可能完成的任务? 2020-11-18 16:30:55 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    Arch ArrayList 与 LinkedList 的区别? 2020-09-20 02:42:49 +0000 UTC
  • Martin Hope
    iluxa1810 哪个更正确使用:if () 或 try-catch? 2020-08-23 18:56:13 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5