C# 在WPF网格单元中绘制可变数量的矩形?
我有一个3列宽8行的WPF网格:C# 在WPF网格单元中绘制可变数量的矩形?,c#,.net,wpf,C#,.net,Wpf,我有一个3列宽8行的WPF网格: <Window x:Class="Container.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="700" Width="1000"&g
<Window x:Class="Container.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="700" Width="1000">
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="10*" />
</Grid.ColumnDefinitions>
</Grid>
</Window>
我用这个来画这样的东西:
第一列和第三列中的每个单元格可能具有不同数量的矩形。此外,每个矩形的宽度可能不同,并在运行时发生变化。宽度将与运行时已知且不断变化的数字成比例
绘制这些矩形的最佳方法是什么?好了,现在 这正是您希望使用数据绑定的目的。如果愿意,您可以尝试手动操作,但如果这样做,您的代码将很快变得非常混乱。WPF允许您以传统的方式进行操作,即与WinForms等类似,但这实际上是为了方便遗留代码的移植。我不会详细介绍MVVM,网上有很多关于MVVM的信息,但您可以先使用NuGet将MVVMLightLibs或其他MVVM框架添加到项目中,然后通过执行以下操作为主窗口分配一个视图模型:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
现在是视图模型本身的时候了,它是您希望视图显示的数据结构的模型:
public class MainViewModel : ViewModelBase
{
public ObservableCollection<PriceLevel> PriceLevels { get; } = new ObservableCollection<PriceLevel>
{
new PriceLevel(110.98, new int[]{ }, new int[]{ }),
new PriceLevel(110.97, new int[]{ }, new int[]{ }),
new PriceLevel(110.96, new int[]{ }, new int[]{ }),
new PriceLevel(110.95, new int[]{ }, new int[]{ 5 }),
new PriceLevel(110.94, new int[]{ }, new int[]{ 3, 8 }),
new PriceLevel(110.93, new int[]{ 8, 3, 5, }, new int[]{ }),
new PriceLevel(110.92, new int[]{ 3 }, new int[]{ }),
new PriceLevel(110.91, new int[]{ }, new int[]{ }),
};
}
public class PriceLevel
{
public double Price { get; }
public ObservableCollection<int> BuyOrders { get; }
public ObservableCollection<int> SellOrders { get; }
public PriceLevel(double price, IEnumerable<int> buyOrders, IEnumerable<int> sellOrders)
{
this.Price = price;
this.BuyOrders = new ObservableCollection<int>(buyOrders);
this.SellOrders = new ObservableCollection<int>(sellOrders);
}
}
看起来有点满了,但如果你把它分解成几个部分,它实际上非常简单。这与您一直尝试做的主要不同之处在于,我使用的是数据网格。这基本上是一个网格控件,添加了额外的功能,使其能够动态响应绑定到的数据。它也有很多额外的东西,我们不需要编辑,列大小调整/重新排序等,所以我把所有这些都关掉了。DataGrid绑定到视图模型中的价格级别,因此它将显示一个垂直列表,显示每个价格级别。然后,我明确声明了您要查找的3列。中间的一个很简单,它只是文本,所以DataGridTextColumn将完成这项工作。另外两个是矩形的水平数组,所以我使用了DataGridTemplateColumn,它允许我精确地定制它们的外观。这种定制主要是在XAML顶部的OrderListStyle中完成的,它将ItemsPanel设置为水平StackPanel,并将ItemTemplate设置为矩形。这里还有一点XAML,根据它在顺序列表中显示的整数的值,将矩形缩放一个常数
结果如下:
我知道XAML看起来有点满,但请记住,现在它已经完全绑定到该视图模型,并且它将自动更新以响应更改。这一点额外的工作在一开始的结果是更干净的更新代码,这也更容易测试和调试
希望这就是你想要的,如果你有任何问题,让我知道,我们可以把它带入聊天
更新:如果您希望看到动态更新正在运行,然后将其添加到主视图模型的构造函数中,它只是随机添加和删除订单:
public MainViewModel()
{
var rng = new Random();
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(0.1);
timer.Tick += (s, e) =>
{
var row = this.PriceLevels[rng.Next(this.PriceLevels.Count())]; // get random row
switch (rng.Next(4))
{
case 0: row.BuyOrders.Add(1 + rng.Next(5)); break;
case 1: row.SellOrders.Add(1 + rng.Next(5)); break;
case 2: if (row.BuyOrders.Count() > 0) row.BuyOrders.RemoveAt(rng.Next(row.BuyOrders.Count())); break;
case 3: if (row.SellOrders.Count() > 0) row.SellOrders.RemoveAt(rng.Next(row.SellOrders.Count())); break;
}
};
timer.Start();
}
这是我经过大约一个小时的摆弄后得出的结论: 我使用MVVM模式使UI尽可能简单。现在,它只是填充了一些随机数据 XAML:
<Window
x:Class="BuySellOrders.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BuySellOrders"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainWindowVm />
</Window.DataContext>
<Grid Margin="15">
<ItemsControl ItemsSource="{Binding Path=Prices}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:PriceEntryVm}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Grid.Column="0"
Padding="5"
HorizontalAlignment="Stretch"
BorderBrush="Black"
BorderThickness="1">
<ItemsControl HorizontalAlignment="Right" ItemsSource="{Binding Path=BuyOrders}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:OrderVm}">
<Border
Width="{Binding Path=Qty}"
Margin="5"
Background="red"
BorderBrush="Black"
BorderThickness="1" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<Border
Grid.Column="1"
BorderBrush="Black"
BorderThickness="1">
<TextBlock
Margin="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=Price}" />
</Border>
<Border
Grid.Column="2"
Padding="5"
BorderBrush="Black"
BorderThickness="1">
<ItemsControl ItemsSource="{Binding Path=SellOrders}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:OrderVm}">
<Border
Width="{Binding Path=Qty}"
Margin="5"
Background="red"
BorderBrush="Black"
BorderThickness="1" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
“我想你可能需要退一步,更详细地描述你想要实现的目标,可能有一个比网格更好的解决方案。”MarkFeldman看到我在问题中添加的图像“你很幸运……除了前世是一名日内交易者外,我还花了两年时间写网络,我国最大的金融交易公司的桌面和移动交易软件。是的,这很有道理首先,一个简单的问题是,您是否愿意以正确的方式进行操作,即使用视图模型和数据绑定?或者在这个阶段,您更愿意使用代码隐藏来完成所有工作吗?好的,让我稍后给您一个答案。这很容易做到,但如果WPF不是你的强项,那么我需要做一些解释。使用MVVM应该很容易。如果设置正确,只需更改、添加和删除一些订单记录,就可以自动传播到用户界面,而无需处理任何特殊事件。马克,这真是太棒了。我有一个问题,我不确定你是否看到我的评论:我将保持行数不变,比如说30行。随着最佳出价/要价的增加或减少,我会等到从顶部或底部算起4行或5行,然后我会重新绘制订单簿,当前的最佳出价/要价现在是中间一行。。您的答案是否允许将最佳出价/要价重新定位到中间行?他使用ObservableCollection这一事实意味着添加或删除行将自动更新UI。一旦集合中有足够的项,只需清除列表,然后用刷新的数据重新填充它。我们的两个答案使用相同的基本概念,你应该能够在我们之间得到一些真正美好的东西。@user997112没问题,别担心!我已经有一段时间没上d了
你的问题引起了我的注意,给了我一个很好的理由去玩一玩。我仍在试图弄清楚我到底想如何让动态缩放工作。这将是我真正感兴趣的部分。ViewModelBase是MVVMLightLibs库使用的基类。可能有点混乱,在这种特殊情况下实际上不需要它。如果您开始认真使用数据绑定,尽管您几乎马上就需要它。它处理诸如属性等的属性更改通知。我的确切示例不需要它,因为我只更新列表,而ObservableCollection为这些列表执行此操作。MainViewModel不需要位于任何特定文件夹中,有些人将虚拟机与视图放在同一文件夹中,其他人将所有虚拟机放在一起。这是一个免费提供的Nuget软件包。嗨,Bradley,非常感谢。如何访问类ViewModel?我需要从某处导入该类吗?一个.net图书馆?我应该将MainWindowVm的代码放在哪里?在项目中的什么地方重要吗?这些C项目,特别是带有GUI代码的项目,对我来说是非常新的。只需安装软件包,你就可以开始了!它只是为INotifyPropertyChanged支持提供了一些快捷方式,这使对属性的更改能够自动更新UI。你可以使用任何你想要的包,比如MVVMLight,或者如果你想要的话手动实现它。
<Window
x:Class="BuySellOrders.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BuySellOrders"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainWindowVm />
</Window.DataContext>
<Grid Margin="15">
<ItemsControl ItemsSource="{Binding Path=Prices}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:PriceEntryVm}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Grid.Column="0"
Padding="5"
HorizontalAlignment="Stretch"
BorderBrush="Black"
BorderThickness="1">
<ItemsControl HorizontalAlignment="Right" ItemsSource="{Binding Path=BuyOrders}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:OrderVm}">
<Border
Width="{Binding Path=Qty}"
Margin="5"
Background="red"
BorderBrush="Black"
BorderThickness="1" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<Border
Grid.Column="1"
BorderBrush="Black"
BorderThickness="1">
<TextBlock
Margin="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=Price}" />
</Border>
<Border
Grid.Column="2"
Padding="5"
BorderBrush="Black"
BorderThickness="1">
<ItemsControl ItemsSource="{Binding Path=SellOrders}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:OrderVm}">
<Border
Width="{Binding Path=Qty}"
Margin="5"
Background="red"
BorderBrush="Black"
BorderThickness="1" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
class MainWindowVm : ViewModel
{
public MainWindowVm()
{
var rnd = new Random();
Prices = new ObservableCollection<PriceEntryVm>();
for (int i = 0; i < 8; i++)
{
var entry = new PriceEntryVm();
Prices.Add(entry);
entry.BuyOrders.CollectionChanged += OnOrderChanged;
entry.SellOrders.CollectionChanged += OnOrderChanged;
entry.Price = (decimal)110.91 + (decimal)i / 100;
var numBuy = rnd.Next(5);
for (int orderIndex = 0; orderIndex < numBuy; orderIndex++)
{
var order = new OrderVm();
order.Qty = rnd.Next(70) + 5;
entry.BuyOrders.Add(order);
}
var numSell = rnd.Next(5);
for (int orderIOndex = 0; orderIOndex < numSell; orderIOndex++)
{
var order = new OrderVm();
order.Qty = rnd.Next(70) + 5;
entry.SellOrders.Add(order);
}
}
}
private void OnOrderChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems)
{
var order = item as OrderVm;
if (order.Qty > LargestOrder)
{
LargestOrder = order.Qty;
}
}
}
}
private int _largestOrder;
public int LargestOrder
{
get { return _largestOrder; }
private set { SetValue(ref _largestOrder, value); }
}
public ObservableCollection<PriceEntryVm> Prices { get; }
}
public class PriceEntryVm: ViewModel
{
public PriceEntryVm()
{
BuyOrders = new OrderList(this);
SellOrders = new OrderList(this);
}
private Decimal _price;
public Decimal Price
{
get {return _price;}
set {SetValue(ref _price, value);}
}
public OrderList BuyOrders { get; }
public OrderList SellOrders { get; }
}
public class OrderList : ObservableCollection<OrderVm>
{
public OrderList(PriceEntryVm priceEntry)
{
PriceEntry = priceEntry;
}
public PriceEntryVm PriceEntry { get; }
}
public class OrderVm : ViewModel
{
private int _qty;
public int Qty
{
get { return _qty; }
set { SetValue(ref _qty, value); }
}
}
_updateTimer = new DispatcherTimer();
_updateTimer.Tick += OnUpdate;
_updateTimer.Interval = TimeSpan.FromSeconds(0.01);
_updateTimer.Start();
private void OnUpdate(object sender, EventArgs e)
{
var entryIndex = _rnd.Next(Prices.Count);
var entry = Prices[entryIndex];
OrderList list;
list = _rnd.Next(2) == 1 ?
entry.BuyOrders :
entry.SellOrders;
if (list.Any())
{
var order = list[_rnd.Next(list.Count)];
order.Qty += _rnd.Next(0, 8) - 4;
}
}