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

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

我当时在想下拉框甚至单击它们时会改变值的标签,但是问题是(根据我所做的测试)添加2500种任何类型的控件会使界面非常慢。我试着在WinForms应用程序中使用tablelayoutpanel并带有suspend / resumeupdate函数,还使用了双重缓冲,这虽然有所帮助,但是仍然非常慢。我不愿意使用DataGridView路由,因为我需要非常自定义的标题,并且当用户更改字段中的值时,我需要UI自动更新一些百分比。但是,如果那是我唯一的选择,我将不会反对。

我听说WPF可能更好,因为您可以有许多控件,并且每个控件都没有自己的Windows句柄,并且具有虚拟化功能(不确定实现的难度)。

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

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

谢谢!

最佳答案

正如@Kerry的答案所表明的那样,winforms几乎所有内容的答案都是“您无法在winforms中做到这一点,因此您需要创建一个更差的替代UI设计,以适应winforms的限制。” -这不是我希望从任何不错的UI框架中得到的。

这是我在WPF中用大约20行C#代码和50行XAML在10分钟内实现的:




在我的机器(I5 CPU和常规视频卡)上,与此WPF UI进行交互时的响应时间为即时。即使没有虚拟化(因为我使用的是不虚拟化的UniformGrid),这也比您希望在Winforms中实现的任何方式都要好。
我根据您的要求引入了编号为1-4的ComboBox
完全可定制(无需诉诸任何“所有者抽奖”技巧)。我什至添加了这些行号和列号,它们当然都是可滚动区域的一部分。
触控就绪-这种大滚动用户界面确实更适合于触摸设备。 Winforms范例甚至没有考虑到这一点。否则,您还可以使用箭头键在网格中实现类似于Excel的键盘导航,以创建更好的非触摸式用户体验。
您还可以轻松地固定行和列标题,同时保持它们与整个网格的滚动偏移量的一致性。
这实际上是一个ListBox,这意味着它具有SelectedItem的概念,并且默认情况下还表现出类似ListBox的视觉样式(您可以在所选项目上看到浅蓝色背景和轮廓)。
通过创建带有项目集合的适当ViewModel,然后使用ItemsControls让WPF完成创建UI的工作,将逻辑与UI分离。在此示例中,没有一行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();
        }
    }
}


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用于任何新项目,仅用于维护旧版应用程序。这是一项古老的技术,不适合满足当今的UI需求。这就是Microsoft创建WPF替代它的原因。
WPF摇滚。只需将我的代码复制并粘贴到File -> New Project -> WPF Application中,然后亲自查看结果。
让我知道您是否需要进一步的帮助。



  重要说明:Windows 8中的WPF默认控件模板比Windows 7中的控件轻得多(以下
  Windows 8删除重型Aero东西的哲学
  所有透明胶片的UI尺寸都较小)。
  
  这意味着在Windows 7上测试我的代码可能不会产生
  就性能而言的预期结果。如果碰巧是
  情况,不用担心。它是可修复的。少量其他XAML
  必须引入(某些StyleControlTemplate)来
  将Windows 7默认设置替换为“更快”。

10-07 19:42
查看更多