C# 在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

我有一个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">
    <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;
    }
}