C# 需要一个非常定制的大型Winforms网格

C# 需要一个非常定制的大型Winforms网格,c#,wpf,winforms,user-interface,datagridview,C#,Wpf,Winforms,User Interface,Datagridview,我将要开发一个Windows PC应用程序(可以是WinForms或WPF),我主要关心的是一个我必须解决的UI问题 基本上,我需要一个大约50x50的网格,我需要从用户那里获取输入。那是2500块地。实际上,大多数将留空,大约10%将由用户填写。每个字段可以为空,也可以是1到4之间的数字。我想要一个简单的输入框——也许是一个下拉框(因为用键盘在所有2500个字段中进行制表是没有意义的,我希望用户用鼠标填写这些值) 我在想,当你点击下拉框或者标签时,它们可能会改变值,但问题是(根据我所做的测试)

我将要开发一个Windows PC应用程序(可以是WinForms或WPF),我主要关心的是一个我必须解决的UI问题

基本上,我需要一个大约50x50的网格,我需要从用户那里获取输入。那是2500块地。实际上,大多数将留空,大约10%将由用户填写。每个字段可以为空,也可以是1到4之间的数字。我想要一个简单的输入框——也许是一个下拉框(因为用键盘在所有2500个字段中进行制表是没有意义的,我希望用户用鼠标填写这些值)

我在想,当你点击下拉框或者标签时,它们可能会改变值,但问题是(根据我所做的测试),添加2500个任何类型的控件都会让界面变得非常慢。我曾尝试在WinForms应用程序中使用tablelayoutpanel,该应用程序具有挂起/恢复更新功能,还具有双缓冲功能,这有点帮助,但速度仍然非常慢。我不愿意选择DataGridView路线,因为我需要非常定制的标题,并且我需要UI在用户更改字段中的值时自动更新一些百分比。但如果这是我唯一的选择,我不会反对

我听说WPF可能更好,因为你可以有很多控件,每个控件都不需要自己的windows句柄,还有虚拟化(不知道实现起来有多困难)

我愿意接受建议。我知道有人会建议我拆开电网,而我最终可能会这么做。无论哪种方式,我都想知道在Windows应用程序中使用多个控件的大型网格的最有效方法,就好像我要在不破坏网格的情况下开发它一样

我正在使用VS2013,在C#,.NET4.0中开发


谢谢

您需要将这些输入分类到逻辑组中

想象一个窗体,类似于VisualStudio中的“属性”窗口。 顶部是一个组合框,列出所有主要逻辑组(飞机、汽车、拖拉机、公共汽车等)。选中后,将显示datagridview,第一列为属性名称,第二列保存值

所有属性都必须具有默认值,并且必须在datagridview中清楚地反映这一点

表单右下角有一个保存按钮,按下该按钮可保存当前逻辑组值

datagridview必须是可滚动的。与“属性”窗口一样,仅显示15-20个项目

当用户从组合框中选择另一个逻辑组时,将加载该逻辑组的属性和值

这是一种更简洁的获取用户输入的方法,用户很容易理解


不要让用户看到一个50x50的网格,希望他们知道应该填写什么,不应该填写什么。假装你是为一个孩子设计的。使其简单易懂。

正如@Kerry的回答所证明的,winforms对几乎所有问题的回答都是“你不能在winforms中做到这一点,因此你需要创建一个更差的替代UI设计,以适应winforms的限制。”-这不是我从任何体面的UI框架中所期望的

这就是我在WPF中用10分钟20行C#代码50行XAML所实现的:

  • 与此WPF UI交互时的响应时间在我的机器(I5 CPU和常规视频卡)上是立即的。即使没有虚拟化(因为我使用的是一个不虚拟化的
    UniformGrid
    ),这也比您在winforms中希望实现的任何功能都要好
  • 根据您的要求,我引入了一个带有数字1-4的组合框
  • 完全可定制(无需借助任何“所有者抽取”技巧)。我甚至添加了这些行数和列数,当然它们都是可滚动区域的一部分
  • 触摸就绪-这种大滚动用户界面更适合触摸设备。winforms范例甚至没有考虑到的东西。否则,您还可以使用箭头键在网格中实现类似Excel的键盘导航,以创建更好的非触摸用户体验
  • 通过小动作还可以使行标题和列标题固定,同时保持它们与整个网格的滚动偏移量的一致性
  • 这实际上是一个
    列表框
    ,这意味着它具有
    SelectedItem
    的概念,并且默认情况下,它还显示类似列表框的视觉样式(您可以看到所选项目上的浅蓝色背景和轮廓)
  • 通过创建一个包含项目集合的适当的视图模型,然后使用它让WPF完成创建UI的工作,逻辑与UI解耦。在本例中,没有一行C代码可以操纵任何UI元素。这一切都是通过美丽来完成的
完整资料来源:

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate x:Key="MarkerTemplate">
            <Border BorderBrush="Gray" BorderThickness="1" Margin="1" Background="Gainsboro">
                <Grid Width="50" Height="30">
                    <TextBlock Text="{Binding}" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                </Grid>
            </Border>
        </DataTemplate>

        <Style TargetType="ListBoxItem">
            <Setter Property="Padding" Value="0"/>
        </Style>
    </Window.Resources>

    <DockPanel>
        <ListBox ItemsSource="{Binding Items}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="Gray" BorderThickness="1">
                    <Grid Width="50" Height="30">
                        <TextBlock Text="{Binding Value}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        <ComboBox x:Name="ComboBox" SelectedItem="{Binding Value}" 
                                  IsDropDownOpen="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
                                  Visibility="Collapsed">
                            <sys:Int32>1</sys:Int32>
                            <sys:Int32>2</sys:Int32>
                            <sys:Int32>3</sys:Int32>
                            <sys:Int32>4</sys:Int32>
                        </ComboBox>
                    </Grid>
                    </Border>
                    <DataTemplate.Triggers>
                        <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="True">
                            <Setter TargetName="ComboBox" Property="Visibility" Value="Visible"/>
                        </DataTrigger>
                    </DataTemplate.Triggers>
                </DataTemplate>
            </ListBox.ItemTemplate>

            <ListBox.Template>
                <ControlTemplate TargetType="ListBox">
                    <ScrollViewer CanContentScroll="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                        <DockPanel>
                            <ItemsControl DockPanel.Dock="Top" ItemsSource="{Binding ColumnMarkers}"
                                ItemTemplate="{StaticResource MarkerTemplate}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <VirtualizingStackPanel Orientation="Horizontal"/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                            </ItemsControl>

                            <ItemsControl DockPanel.Dock="Left" ItemsSource="{Binding RowMarkers}"
                                          ItemTemplate="{StaticResource MarkerTemplate}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <VirtualizingStackPanel Orientation="Vertical"/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                            </ItemsControl>

                            <UniformGrid Rows="50" Columns="50" IsItemsHost="True"/>
                        </DockPanel>
                    </ScrollViewer>
                </ControlTemplate>
            </ListBox.Template>
        </ListBox>
    </DockPanel>
</Window>
视图模型:

public class ViewModel
{
    public List<string> RowMarkers { get; set; }

    public List<string> ColumnMarkers { get; set; }

    public ObservableCollection<Item> Items { get; set; }

    public ViewModel()
    {
        RowMarkers = Enumerable.Range(1, 50).Select(x => x.ToString()).ToList();
        ColumnMarkers = new[] { " " }.Concat(Enumerable.Range(1, 50).Select(x => x.ToString())).ToList();

        var list = new List<Item>();

        for (int i = 0; i < 50; i++)
        {
            for (int j = 0; j < 50; j++)
            {
                list.Add(new Item());
            }
        }

        Items = new ObservableCollection<Item>(list);
    }
}
  • 您可能需要将
    属性添加到
    类中,以便跟踪哪些行/列实际包含值。然后您可以像这样使用LINQ:

    var values = Items.Where(x => Value != null);
    
    并获取
    s的列表,并获取每个项的
    项.行
    项.列

  • 忘记winforms。它完全没有用。-在这一点上,winforms已经完全过时了。无论您使用winforms可以实现什么,您都可以在WPF中实现同样的功能,代码量可以减少10%,并且可能会获得更好的结果。winforms不建议用于任何新项目,仅用于维护遗留应用程序。这是一项古老的技术,不适合满足今天的用户界面需求。这就是为什么微软创建了WPF来取代它

  • WPF岩石。只需复制并粘贴我的
    public class Item
    {
        public int? Value { get; set; }
    }
    
    var values = Items.Where(x => Value != null);
    
    public class ValuesPanel : Panel
    {
        public int this[int x, int y] { get { return cells[x, y]; } set { cells[x, y] = value; } }
        private int[,] cells = new int[50, 50];
        const int cell_width = 15, cell_height = 15;
        private int x, y, scroll_x, scroll_y;
    
        public ValuesPanel()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true);
            AutoScrollMinSize = new Size(50 * cell_width, 50 * cell_height);
            BorderStyle = BorderStyle.Fixed3D;
            Cursor = Cursors.Hand;
        }
    
        protected override void OnPaint(PaintEventArgs e)
        {
            e.Graphics.FillRectangle(Brushes.DeepSkyBlue, x * cell_width - scroll_x, y * cell_height - scroll_y, cell_width, cell_height);
            for (int j = 0; j < 50; j++)
                for (int i = 0; i < 50; i++)
                {
                    int xx = i * cell_width - scroll_x, yy = j * cell_height - scroll_y;
                    e.Graphics.DrawString(cells[i, j].ToString(), Font, new SolidBrush(ForeColor), new PointF(3 + xx, 1 + yy));
                    e.Graphics.DrawLine(Pens.Gray, xx, yy, xx + cell_width - 2, yy);
                    e.Graphics.DrawLine(Pens.Gray, xx, yy + cell_height - 2, xx, yy);
                    e.Graphics.DrawLine(Pens.White, xx + 1, yy + cell_height - 1, xx + cell_width - 1, yy + cell_height - 1);
                    e.Graphics.DrawLine(Pens.White, xx + cell_width - 1, yy + 1, xx + cell_width - 1, yy + cell_height - 1);
                }
        }
    
        protected override void OnScroll(ScrollEventArgs se)
        {
            Invalidate();
            base.OnScroll(se);
            scroll_x = HorizontalScroll.Value;
            scroll_y = VerticalScroll.Value;
        }
    
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            x = Math.Min(49, (e.Location.X + scroll_x) / cell_width);
            y = Math.Min(49, (e.Location.Y + scroll_y) / cell_height);
            Invalidate();
        }
    
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            if (e.Button == MouseButtons.Left)
                cells[x, y] = (cells[x, y] % 4) + 1;
            else if (e.Button == MouseButtons.Right)
                cells[x, y] = cells[x, y] < 2 ? 4 : cells[x, y] - 1;
        }
    }