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

时间:2014-02-20 00:24:10

标签: c# wpf winforms user-interface datagridview

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

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

我在考虑下拉框或者甚至是标签,当你点击它们时会改变价值,但问题是(从我做过的测试中)添加2500种任何类型的控件会使界面非常慢。我尝试在winForms应用程序中使用带有suspend / resumeupdate函数的tablelayoutpanel,还有doublebuffering,这有点帮助,但它仍然非常慢。我不愿意去DataGridView路由,因为我需要非常自定义标头,我需要UI来自动更新一些百分比,因为用户更改了字段中的值。但如果这是我唯一的选择,我不会反对。

我听说WPF可能会更好,因为你可以有很多控件,而且每个控件都没有自己的窗口处理,而且还有虚拟化(不确定实现有多难)。

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

我正在使用VS 2013,在C#,.NET 4.0中开发。

谢谢!

4 个答案:

答案 0 :(得分:4)

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

这是我在 WPF 10分钟中实现的,其中包含 20行C#代码 50行XAML < /强>:

enter image description here

  • 在我的计算机(I5 CPU和普通视频卡)上与此WPF UI交互时的响应时间为立即。即使没有虚拟化(因为我使用的是不会虚拟化的UniformGrid),这比你希望在winforms中实现的任何东西都要好。
  • 我根据您的要求推出了一个数字为1-4的ComboBox
  • 完全可自定义(无需借助任何“所有者抽奖”黑客攻击)。我甚至添加了这些行和列号,当然这些都是可滚动区域的一部分。
  • Touch-Ready - 这种大滚动的UI真的更适合触摸设备。 winforms范例甚至没有考虑到的东西。否则,您还可以使用箭头键在网格中实现类似Excel的键盘导航,以创建更好的非触摸用户体验。
  • 使用小功夫,您还可以使行和列标题固定,同时保持与整个网格的滚动偏移的一致性。
  • 这实际上是一个ListBox,这意味着它具有SelectedItem的概念,默认情况下它也展示了类似ListBox的视觉风格(你可以看到浅蓝色背景和轮廓上的选定项目)。
  • 通过使用一组Items创建一个正确的 ViewModel ,然后使用ItemsControls让WPF完成创建工作,逻辑解耦用户界面。在这个例子中,没有一行C#代码可以操纵任何UI元素。这一切都是通过漂亮的DataBinding完成的。

完整来源:

<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>

代码背后:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;

namespace WpfApplication3
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new ViewModel();
        }
    }
}

视图模型:

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);
    }
}

数据项:

public class Item
{
    public int? Value { get; set; }
}
  • 您可能希望将RowColumn属性添加到Item类,以便您可以跟踪实际包含值的行/列。然后您可以像这样使用LINQ:

    var values = Items.Where(x => Value != null);
    

    并获取Item的列表,并为每个人获取item.RowItem.Column

  • 忘记winforms。这完全没用。 - 此时,winforms已经完全过时了。无论你使用winforms可以实现什么,你都可以在WPF中实现相同的功能,代码量可以达到10%,并且可能会有更好的结果。不建议将winforms用于任何新项目,仅用于维护旧版应用程序。这是一项古老的技术,不适合满足当今用户界面的需求。这就是微软为替换而创建WPF的原因。

  • WPF Rocks 。只需将我的代码复制并粘贴到File -> New Project -> WPF Application中,然后自行查看结果。

  • 如果您需要进一步的帮助,请与我们联系。

  

重要说明: WPF默认控件模板在Windows 8中的重量比Windows 7的重量轻得多(以下是   Windows 8的理念是去除沉重的Aero东西和实际上   所有透明胶片的UI占用空间都较小)。

     

这意味着在Windows 7上测试我的代码可能不会产生   绩效方面的预期结果。如果那恰好是   情况,别担心。它是可以修复的。少量额外的XAML   必须引入(一些StyleControlTemplate)   用“更快”的东西替换Windows 7默认值。

答案 1 :(得分:4)

虽然您已经得到了答案并且这是一篇旧帖子,但我会使用无用的替代为您提供解决方案,仅用于体育运动并向某些人展示它并不像他们想象的那么无用。当然,在WPF中做一些事情会更快( IF 你很清楚)并且大多数时候可能更短,但如果你是一个真正的程序员,那就没有这样的事情了。作为&#39;无用的框架&#39;。

这只是一个原始的例子(无论如何你都不会使用它)所以不要那么挑剔。它可以使用一些图像用于细胞以获得更好的外观等,但这并不重要。无论如何,它是一个完整的控件(不需要设计师代码),所以如果你将代码粘贴到你的解决方案的某个地方,你就可以将它放在你的工具箱上的表格而你#&# 39;立即看看它的外观。运行它来测试行为。使用[x,y]对其进行索引,以编程方式获取或设置值。

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;
    }
}

我真的很喜欢WPF,但是我无法让某人呼叫WinForms&#34;无用&#34;。继续编码! ;)

答案 2 :(得分:0)

您需要将这些输入分类为逻辑组。

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

所有属性都必须具有默认值,并且必须在datagridview中清楚地反映出来。

表单右下角的“保存”按钮可用,按下该按钮可保存当前逻辑组值。

datagridview必须是可滚动的。您只显示“属性”窗口中的15-20项。

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

这是一种更清晰的用户输入方式,用户可以轻松理解。

不要向用户展示50x50网格,希望他们知道要填写什么,不要填写什么。假装你是为一个孩子设计的。使其简单易懂,易于理解。

答案 3 :(得分:0)

也许,我迟到了这个派对,但我在浏览StackOverflow时偶然发现了这个老问题。我在答案中没有看到WinForms解决方案,我认为我的答案可以帮助人们只为这个平台寻找解决方案。

以下是我在一分钟内使用iGrid WinForms grid实施的解决方案:

private iGDropDownList myCombo = new iGDropDownList();

private void Form1_Load(object sender, EventArgs e)
{
    myCombo.Items.Add(1);
    myCombo.Items.Add(2);
    myCombo.Items.Add(3);
    myCombo.Items.Add(4);

    iGrid1.DefaultCol.CellStyle.DropDownControl = myCombo;

    iGrid1.Cols.Count = 50;
    iGrid1.Rows.Count = 50;
}

相应的截图:

Cell combo box WinForms grid

如果需要,可以通过将ShowControlsInAllCells属性设置为false来隐藏所有组合框按钮。

将iGrid用于此类解决方案的主要好处之一是即使在廉价的低端平板电脑上也能非常快速地运行。 iGrid的单元格不包含真正的组合框控件,所有这些都是在高度优化的绘图代码中绘制的,仅在视口中当前可见的单元格中。因此,对于iGrid来说,无论是显示50行还是500行,都没有区别 - 内容将以相同的速度绘制和滚动。

您可以补充一点,即使在2017年,iGrid仍在开发和支持中!