Рисовать переменное количество прямоугольников в ячейках WPF Grid?

И это будет моим вкладом в эту тему. Я думаю, что .reduce() - лучший способ.

var segment = (arr, n) => arr.reduce((r,e,i) => i%n ? (r[r.length-1].push(e), r)
                                                    : (r.push([e]), r), []),
        arr = Array.from({length: 31}).map((_,i) => i+1);
        res = segment(arr,7);
console.log(JSON.stringify(res));

Но вышеприведенная реализация не очень эффективна, поскольку .reduce() проходит через все arr. Более эффективный подход (очень близкий к самому быстрому императивному решению) будет, итерации по уменьшенному (чтобы быть разбитым) массивом, поскольку мы можем заранее вычислить его размер на Math.ceil(arr/n);. Когда у нас есть пустой массив результатов, подобный Array(Math.ceil(arr.length/n)).fill();, остальное состоит в отображении в него ломтиков массива arr.

function chunk(arr,n){
  var r = Array(Math.ceil(arr.length/n)).fill();
  return r.map((e,i) => arr.slice(i*n, i*n+n));
}

arr = Array.from({length: 31}).map((_,i) => i+1);
res = chunk(arr,7);
console.log(JSON.stringify(res));

0
задан user997112 6 March 2019 в 00:28
поделиться

2 ответа

Вот то, что я придумал после примерно часа возни ( GitHub Repo ): enter image description here

Я используя шаблон MVVM, чтобы сделать интерфейс максимально простым. Прямо сейчас, это просто заполняется некоторыми случайными данными.

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>

Модели представления:

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); }
    }

}

Мне пришлось сделать некоторые предположения о наименовании вещей, но, надеюсь, вы должны получить базовое представление о том, что продолжается.

Он структурирован как список PriceEntry, каждый из которых содержит свойства Price, а также свойства BuyOrders и SellOrders.

BuyOrders и SellOrders - это просто списки заказов, которые имеют свойство Quantity.

XAML связывает список ценовых записей с шаблоном, который содержит сетку из 3 столбцов. Первый и третий столбцы этой сетки привязаны к другому набору элементов управления для каждого списка заказов. Шаблон для каждого заказа - это просто граница с Width, привязанным к Quantity заказа.

Все привязки означают, что простое обновление свойства или добавление ордера в список покупки или продажи записи цены будет автоматически распространяться на пользовательский интерфейс. Добавление или удаление PriceEntry также автоматически настроит пользовательский интерфейс.

Я еще не реализовал ваше автоматическое масштабирование, но основная идея заключается в том, чтобы использовать ValueConverter для привязки Quantity, чтобы она автоматически настраивалась на самый большой порядок.

В качестве дополнительного примечания, он использует этот пакет nuget для предоставления некоторого кода стандартного кода MVVM, но вы должны иметь возможность использовать все, что захотите, до тех пор, пока он вам дает INotifyPropertyChanged поддержка.


Здесь приведен дополнительный снимок экрана, показывающий динамический характер MVVM, обновляющего пользовательский интерфейс на основе таймера.

enter image description here

Для этого понадобилось всего несколько строк кода, чтобы случайным образом выбрать строку, затем случайным образом выбрать порядок в строке, затем добавить или вычесть небольшой случайное количество из количества.

_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;
    }
}
0
ответ дан Bradley Uffner 6 March 2019 в 00:28
поделиться

Прямо здесь, идет ...

Это именно та вещь, для которой вы хотите использовать привязку данных. Вы можете попытаться сделать что-то вручную, если хотите, но ваш код быстро станет очень грязным, если вы захотите. WPF позволяет вам делать вещи по-старому (то есть, аналогично WinForms и др.), Но это действительно облегчало перенос устаревшего кода. Я не буду вдаваться в подробности о 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);
    }
}
[1124 ] Если вы еще не знаете, ObservableCollection очень похож на список, но он поддерживает уведомление об изменениях, поэтому, когда вы заставляете ваш вид отображать данные в нем, ваш графический интерфейс будет обновляться автоматически при каждом изменении списка. Этот класс MainViewModel содержит ObservableCollection типа PriceLevel, а каждый PriceLevel содержит цену и списки ордеров на покупку и продажу. Это означает, что вы сможете добавлять и удалять ценовые точки, а также добавлять и удалять ордера в ценовых точках, и ваш пользовательский интерфейс будет отражать эти изменения.

Итак, о самом внешнем интерфейсе:

<Window.Resources>

    <!-- Style to display order list as horizontal list of red rectangles -->
    <Style x:Key="OrderListStyle" TargetType="{x:Type ItemsControl}">
        <!-- Set ItemsPanel to a horizontal StackPanel -->
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <!-- Display each item in the order list as a red rectangle and scale x by 8*size -->
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Border BorderBrush="Black" BorderThickness="1" Margin="5" >
                        <Rectangle Width="{Binding}" Height="20" Fill="Red">
                            <Rectangle.LayoutTransform>
                                <ScaleTransform ScaleX="8" ScaleY="1" />
                            </Rectangle.LayoutTransform>
                        </Rectangle>
                    </Border>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- Style to make Price cells vertically aligned -->
    <Style TargetType="{x:Type DataGridCell}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type DataGridCell}">
                    <Grid Background="{TemplateBinding Background}">
                        <ContentPresenter VerticalAlignment="Center" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- This style centers the column's header text -->
    <Style TargetType="DataGridColumnHeader">
        <Setter Property="HorizontalContentAlignment" Value="Center" />
    </Style>

</Window.Resources>

<!-- This datagrid displays the main list of PriceLevels -->
<DataGrid ItemsSource="{Binding PriceLevels}" AutoGenerateColumns="False" IsReadOnly="True"
          CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserResizeColumns="False"
          CanUserResizeRows="False" CanUserSortColumns="False" RowHeight="30">

    <DataGrid.Columns>

        <!-- The buy orders column -->
        <DataGridTemplateColumn Header="Buy orders" Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ItemsControl ItemsSource="{Binding BuyOrders}" Style="{StaticResource OrderListStyle}" HorizontalAlignment="Right" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

        <!-- The price column -->
        <DataGridTextColumn Header="Price" Width="Auto" Binding="{Binding Price}" />

        <!-- The sell orders column -->
        <DataGridTemplateColumn Header="Sell Orders" Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ItemsControl ItemsSource="{Binding SellOrders}" Style="{StaticResource OrderListStyle}" HorizontalAlignment="Left" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

Выглядит немного полным, но если разбить его на разделы, это на самом деле довольно просто. Основное различие между этим и тем, что вы пытались сделать, заключается в том, что я использую DataGrid. По сути, это элемент управления Grid, в который добавлены дополнительные функции, позволяющие динамически реагировать на данные, с которыми он связан. В нем также есть много дополнительных вещей, которые нам не нужны (редактирование, изменение размера / перестановка столбцов и т. Д.), Поэтому я отключил все это. DataGrid привязывается к PriceLevels в модели представления, поэтому он будет отображать вертикальный список, показывающий каждый из них. Затем я явно объявил 3 столбца, которые вы ищете. Середина проста, это просто текст, поэтому DataGridTextColumn сделает это. Два других - это горизонтальные массивы прямоугольников, поэтому я использовал DataGridTemplateColumn, который позволяет мне точно настроить их внешний вид. Эта настройка в основном выполняется в OrderListStyle в самом верху XAML, который устанавливает ItemsPanel в горизонтальное положение StackPanel и устанавливает в ItemTemplate прямоугольник. Там также есть немного XAML для масштабирования прямоугольника по константе, в соответствии со значением целого числа, которое он отображает в списке заказов.

Вот результат:

enter image description here

Я знаю, что 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();
}
0
ответ дан marc_s 6 March 2019 в 00:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: