我即将开发一个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中开发。
谢谢!
答案 0 :(得分:4)
正如@Kerry的答案所证明的那样,winforms对几乎所有内容的回答都是“你不能在winforms中做到这一点,因此你需要创建一个更适合winforms限制的更差的替代UI设计。” - 这不是我对任何体面的UI框架的期望。
这是我在 WPF 10分钟中实现的,其中包含 20行C#代码和 50行XAML < /强>:
UniformGrid
),这比你希望在winforms中实现的任何东西都要好。ComboBox
。ListBox
,这意味着它具有SelectedItem
的概念,默认情况下它也展示了类似ListBox的视觉风格(你可以看到浅蓝色背景和轮廓上的选定项目)。完整来源:
<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; }
}
您可能希望将Row
和Column
属性添加到Item
类,以便您可以跟踪实际包含值的行/列。然后您可以像这样使用LINQ:
var values = Items.Where(x => Value != null);
并获取Item
的列表,并为每个人获取item.Row
和Item.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 必须引入(一些
Style
和ControlTemplate
) 用“更快”的东西替换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;
}
相应的截图:
如果需要,可以通过将ShowControlsInAllCells属性设置为false
来隐藏所有组合框按钮。
将iGrid用于此类解决方案的主要好处之一是即使在廉价的低端平板电脑上也能非常快速地运行。 iGrid的单元格不包含真正的组合框控件,所有这些都是在高度优化的绘图代码中绘制的,仅在视口中当前可见的单元格中。因此,对于iGrid来说,无论是显示50行还是500行,都没有区别 - 内容将以相同的速度绘制和滚动。
您可以补充一点,即使在2017年,iGrid仍在开发和支持中!