我将要开发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; }
}
您可能希望将
Row
和Column
属性添加到Item
类,以便可以跟踪实际包含值的行/列。然后,您可以像这样使用LINQ:var values = Items.Where(x => Value != null);
并获取
Item
的列表,并分别获取item.Row
和Item.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
必须引入(某些
Style
和ControlTemplate
)来将Windows 7默认设置替换为“更快”。