C# 需要一个非常定制的大型Winforms网格
我将要开发一个Windows PC应用程序(可以是WinForms或WPF),我主要关心的是一个我必须解决的UI问题 基本上,我需要一个大约50x50的网格,我需要从用户那里获取输入。那是2500块地。实际上,大多数将留空,大约10%将由用户填写。每个字段可以为空,也可以是1到4之间的数字。我想要一个简单的输入框——也许是一个下拉框(因为用键盘在所有2500个字段中进行制表是没有意义的,我希望用户用鼠标填写这些值) 我在想,当你点击下拉框或者标签时,它们可能会改变值,但问题是(根据我所做的测试),添加2500个任何类型的控件都会让界面变得非常慢。我曾尝试在WinForms应用程序中使用tablelayoutpanel,该应用程序具有挂起/恢复更新功能,还具有双缓冲功能,这有点帮助,但速度仍然非常慢。我不愿意选择DataGridView路线,因为我需要非常定制的标题,并且我需要UI在用户更改字段中的值时自动更新一些百分比。但如果这是我唯一的选择,我不会反对 我听说WPF可能更好,因为你可以有很多控件,每个控件都不需要自己的windows句柄,还有虚拟化(不知道实现起来有多困难) 我愿意接受建议。我知道有人会建议我拆开电网,而我最终可能会这么做。无论哪种方式,我都想知道在Windows应用程序中使用多个控件的大型网格的最有效方法,就好像我要在不破坏网格的情况下开发它一样 我正在使用VS2013,在C#,.NET4.0中开发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个字段中进行制表是没有意义的,我希望用户用鼠标填写这些值) 我在想,当你点击下拉框或者标签时,它们可能会改变值,但问题是(根据我所做的测试)
谢谢 您需要将这些输入分类到逻辑组中 想象一个窗体,类似于VisualStudio中的“属性”窗口。 顶部是一个组合框,列出所有主要逻辑组(飞机、汽车、拖拉机、公共汽车等)。选中后,将显示datagridview,第一列为属性名称,第二列保存值 所有属性都必须具有默认值,并且必须在datagridview中清楚地反映这一点 表单右下角有一个保存按钮,按下该按钮可保存当前逻辑组值 datagridview必须是可滚动的。与“属性”窗口一样,仅显示15-20个项目 当用户从组合框中选择另一个逻辑组时,将加载该逻辑组的属性和值 这是一种更简洁的获取用户输入的方法,用户很容易理解
不要让用户看到一个50x50的网格,希望他们知道应该填写什么,不应该填写什么。假装你是为一个孩子设计的。使其简单易懂。正如@Kerry的回答所证明的,winforms对几乎所有问题的回答都是“你不能在winforms中做到这一点,因此你需要创建一个更差的替代UI设计,以适应winforms的限制。”-这不是我从任何体面的UI框架中所期望的 这就是我在WPF中用10分钟和20行C#代码和50行XAML所实现的:
- 与此WPF UI交互时的响应时间在我的机器(I5 CPU和常规视频卡)上是立即的。即使没有虚拟化(因为我使用的是一个不虚拟化的
),这也比您在winforms中希望实现的任何功能都要好UniformGrid
- 根据您的要求,我引入了一个带有数字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; } }