2

Существует последовательность фигур определённого цвета, которая создаётся динамически при нажатии кнопки, а так же добавляется в WrapPanel.

private void BoxGenerator(int length)
    {
        Path boxpath = new Path();
        PathGeometry boxgeom = new PathGeometry();
        PathFigure boxfigure = new PathFigure();
    LineSegment ln1 = new LineSegment();
    LineSegment ln2 = new LineSegment();
    LineSegment ln3 = new LineSegment();





    boxfigure.StartPoint = new Point(0, 5);
    ln1.Point = new Point(0, -5 * length);
    ln2.Point = new Point(20, -5 * length);
    ln3.Point = new Point(20, 5);
    boxfigure.IsClosed = true;
    boxfigure.Segments.Add(ln1);
    boxfigure.Segments.Add(ln2);
    boxfigure.Segments.Add(ln3);
    boxgeom.Figures.Add(boxfigure);

    boxpath.Data = boxgeom;
    boxpath.Stroke = Brushes.Green;
    boxpath.StrokeThickness = 5;
    boxpath.Fill = new SolidColorBrush(Color.FromRgb(0, 0, 0));           
    PanelBoxes.Children.Add(boxpath);

} 

private void InitialBut_Click(object sender, RoutedEventArgs e) {

    PanelBoxes.Children.Clear();
    string text = NumberBox.Text.Trim().ToString();
    text = Regex.Replace(text, "[ ]+", " ");

    bool error = false;
    if (text == "")
    {
        ErrorLabel.Content = "Заполните ячейку ввода цифр";
        return;
    }
    foreach (char symbol in text)
    {

        if(!Char.IsDigit(symbol) && (symbol != 32) && (symbol != 45))
        {
            error = true;

        }
    }
    if (error)
    {
        ErrorLabel.Content = "В наборе содержатся лишние символы, повторите ввод.";
        NumberBox.Text = "";
        return;
    }else
    {
        ErrorLabel.Content = "Полученная последовательность:"+text;

    }


    foreach (int elem in text.Split(' ').Select(int.Parse))
    {
        unsortList.Add(elem);
        BoxGenerator(elem);
    }


}

Далее, по нажатию кнопки я пытаюсь изменить цвет некоторых блоков, но мне не удаётся получить к ним доступ, т.к. они находятся в WrapPanel. Вопрос: Как получить к ним доступ и как изменить у определённых элементов цвет? Пытался сделать так, но безуспешно

Path box = PanelBoxes.Children.OfType<Path>().First();
        box.Fill = new SolidColorBrush(Color.FromRgb(100, 100, 0));

Должно быть так: При нажатии кнопки(выделена красным), менялся цвет блока (выделено синим)Изменение цвета блока

return
  • 2,740
  • Должно быть так - а как сейчас? Вы уверены, что обработчик события нажатия кнопки прицеплен к той кнопке, от которой ожидается действие? Ваша проблема за пределами показанного кода. Кстати, если расскажете, в чем смысл приложения, и какую задачу вы решаете, возможно подскажу, как решить это в WPF правильно, потому что пока видно, что вы не знакомы с инструментами WPF, а решаете "в лоб". – aepot Aug 20 '20 at 08:20
  • 2
    Я пытаюсь визуально показать как работают сортировки, т.е. каждый блок это какое-то число, которое вводит пользователь в строку. После нажатия кнопки(например пузырьком) должна по шагам демонстрироваться перестановка блоков(сортировка). Все блоки изначально не отсортированы. Далее выбранный блок подсвечивается(меняется на другой цвет) и переставляется на нужную позицию – GamesBro Aug 20 '20 at 08:31

1 Answers1

2

Несмотря на то, что вопрос явно не содержит источника проблемы, которую вы пытаетесь победить, задача выглядит интересно, и я покажу ее решение. Проблема скорее всего в том, что в процессе сортировки вы исполняете синхронный код, и интерфейс просто не обновляется до тех пор, пока метод не закончит свою работу. Для исправления данной проблемы лучше использовать асинхронный подход к реализации (см. Асинхронное программирование).

Второй момент заключается в том, что вы используете подход, не являющийся эффективным в WPF. Так как в процессе написания кода вам приходится взаимодействовать напрямую с контролами, а интерфейс контролов не оптимизирован для такого взаимодействия в WPF, он просто неудобен. Такой способ реализации больше подходит к WinForms.

В WPF для максимального удобства при написании кода лучше всего использовать привязки Binding, команды ICommand и отделить логику работы программы от самого интерфейса приложения, оповещая его с помощью реализации интерфейса INotifyPropertyChanged. А арихитектурно эти методики удобнее всего использовать в рамках шаблона программирования MVVM. Этот шаблон программирования очень популярен в WPF и UWP приложениях, и если интересно, вы сможете найти очень много примеров его применения, в том числе здесь на StackOverflow на русском.

Я написал демонстрационное приложение, которое делает примерно то, что вы задумали.

Часть 1. Вспомогательные классы

Эти классы предназначены для удобства при использовании команд и оповещения интерфейса.

NotifyPropertyChanged.cs

public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Я буду наследоваться от этого класса там, где мне надо оповещать интерфейс об изменениях свойства, чтобы сам интерфейс себя обновил. Делать это смогу с помощью простого вызова OnPropertyChanged() (см. код ниже).

RelayCommand.cs

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
    add =&gt; CommandManager.RequerySuggested += value;
    remove =&gt; CommandManager.RequerySuggested -= value;
}

public RelayCommand(Action&lt;object&gt; execute, Predicate&lt;object&gt; canExecute = null)
    =&gt; (_execute, _canExecute) = (execute, canExecute);

public bool CanExecute(object parameter)
    =&gt; _canExecute == null || _canExecute(parameter);

public void Execute(object parameter)
    =&gt; _execute(parameter);

}

Родной API команд в WPF не то чтобы максимально удобен, и чтобы сделать реализацию команд в коде максимально простой, я буду использовать этот класс для создания экземпляра команды. Если кратко, то команды - это то что я буду использовать вместо обработчиков события Click в кнопках, но их много где в WPF можно использовать.

Часть 2. Реализация квадратика

Square.cs

public class Square : NotifyPropertyChanged
{
    private bool _isActive;
public int Number { get; } // сюда привязано число внутри квадратика

public int Size =&gt; 20 + Number * 5; // сюда привязан размер квадратика

public bool IsActive // сюда привязан цвет фона квадратика
{
    get =&gt; _isActive;
    set
    {
        _isActive = value;
        OnPropertyChanged();
    }
}

public Square(int number)
{
    Number = number;
}

}

Эта модель позволяет интерфейсу нарисовать квадрат нужного размера и вписать в него нужное число.

Часть 3. View Model

MainViewModel.cs

public class MainViewModel : NotifyPropertyChanged
{
    private static readonly Random _rnd = new Random();
private ObservableCollection&lt;Square&gt; _numbers;
private ICommand _shuffleItems;
private ICommand _sortItems;
private bool _buttonsEnabled = true;

// задержка анимации квадратиков в миллисекундах
public int Delay { get; set; } = 250;

// сюда привязана кнопка, перемешивающая квадратики
public ICommand ShuffleItems =&gt; _shuffleItems ?? (_shuffleItems = new RelayCommand(parameter =&gt;
{
    ShuffleNumbers();
}));

// сюда привязана кнопка, сортирующая квадратики
public ICommand SortItems =&gt; _sortItems ?? (_sortItems = new RelayCommand(parameter =&gt;
{
    SortNumbers();
}));

// коллекция квадратиков
public ObservableCollection&lt;Square&gt; Numbers
{
    get =&gt; _numbers;
    set
    {
        _numbers = value;
        OnPropertyChanged();
    }
}

// сюда привязана доступность кнопок, для защиты от повторного нажатия
public bool ButtonsEnabled
{
    get =&gt; _buttonsEnabled;
    set
    {
        _buttonsEnabled = value;
        OnPropertyChanged();
    }
}

public MainViewModel() // конструктор
{
    Numbers = new ObservableCollection&lt;Square&gt;(NumberSequence(1, 10));
}

// меняет цвет фона перемещаемого квадратика и меняет его местами с другим квадратиком
private async Task ExchangeNumberAsync(int oldIndex, int newIndex)
{
    if (oldIndex == newIndex) 
        return;

    Numbers[oldIndex].IsActive = true; // перекрашивает квадратик в красный

    if (Delay &gt; 0) 
        await Task.Delay(Delay);

    Square item = Numbers[oldIndex];
    Numbers[oldIndex] = Numbers[newIndex];
    Numbers[newIndex] = item;

    if (Delay &gt; 0) 
        await Task.Delay(Delay);

    Numbers[newIndex].IsActive = false; // перекрашивает квадратик в черный

    if (Delay &gt; 0) 
        await Task.Delay(Delay);
}

// перемешивает квадратики
private async void ShuffleNumbers()
{
    ButtonsEnabled = false;
    try
    {
        for (int i = 0; i &lt; Numbers.Count; i++)
        {
            await ExchangeNumberAsync(i, _rnd.Next(0, Numbers.Count));
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    finally
    {
        ButtonsEnabled = true;
    }
}

// сортирует квадратики пузырьком
private async void SortNumbers()
{
    ButtonsEnabled = false;
    try
    {
        for (int i = 0; i &lt; Numbers.Count; i++)
        {
            for (int j = i + 1; j &lt; Numbers.Count; j++)
            {
                if (Numbers[i].Number &gt; Numbers[j].Number)
                {
                    await ExchangeNumberAsync(i, j);
                }
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    finally
    {
        ButtonsEnabled = true;
    }
}

// если не в курсе, то обязательно поинтересуйтесь, как работают энумераторы
private IEnumerable&lt;Square&gt; NumberSequence(int start, int length)
{
    for (int i = start; i &lt; start + length; i++)
    {
        yield return new Square(i);
    }
}

}

Часть 4. View

А вот и полная разметка интерфейса.

MainWindow.xaml

<Window x:Class="SortTest.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SortTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainViewModel/><!-- здесь подключается View Model -->
    </Window.DataContext>
    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Margin" Value="5"/>
            <Setter Property="Padding" Value="10,5"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <StackPanel>
            <Button Content="Перемешать" IsEnabled="{Binding ButtonsEnabled}" Command="{Binding ShuffleItems}"/>
            <Button Content="Сортировать" IsEnabled="{Binding ButtonsEnabled}" Command="{Binding SortItems}"/>
        </StackPanel>
        <ItemsControl Grid.Column="1" ItemsSource="{Binding Numbers}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border Width="{Binding Size}" Height="{Binding Size}" BorderBrush="Green" BorderThickness="2" Margin="3">
                        <Border.Style>
                            <Style TargetType="Border">
                                <Setter Property="Background" Value="Black"/>
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding IsActive}" Value="True">
                                        <Setter Property="Background" Value="Red"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Border.Style>
                        <TextBlock Text="{Binding Number}" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="18" FontWeight="Bold"/>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</Window>

Вот, собственно и всё. В классе MainWindow у меня ничего нет, там пусто.

MainWindows.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

введите сюда описание изображения

P.S. Опытные разработчики, не ругайте меня за async void, здесь он использован безопасно, использовал его для упрощения примера. Для автора вопроса: в статье по ссылке в начале ответа рассказано, почему async void - плохо. Так же способ указания DataContext здесь самый простой, но не оптимальный, подробнее можно узнать здесь.

aepot
  • 49,560
  • Целиком проект доступен по ссылке - https://yadi.sk/d/fnW36nGGxM_wsg (ссылка временная) – aepot Aug 20 '20 at 12:02
  • 1
    Я вас поругаю за 3 вещи: 1. Зачем вы везде засовываете INPC? Например, Numbers, это свойство не меняется у вас, оно статично. 2. DataContext в XAML плохо!. 3. MessageBox не лучший вариант для отображения диалогов напрямую, ибо как минимум это View слой и по сути нарушение MVVM, если нужен MessageBox, то стоит сделать его как некий сервис и использовать интерфейс, не связанный с основной реализацией. – EvgeniyZ Aug 20 '20 at 12:31
  • @EvgeniyZ согласен по всем пунктам, но это и не чистый MVVM, я всего-лишь руководствовался им как подходом к реализации. Здесь по-хорошему Model еще должна быть, а ее нет. Отмажусь: 1) Для демонстрации, я даже выкинул INPC из Square.Number, вспоминая как вы меня ругали. 2) Для простоты, я бы не хотел ради такой простой задачи переопределять OnStartup() и принципиально не хотел модифицировать xaml.cs. 3) Можно было StatusPanel в UI засунуть, но тоже не стал в сторону упрощения. :) Спасибо за замечания, все по делу. Но для новичка здесь и так куча информации, я урезал как мог. – aepot Aug 20 '20 at 12:57
  • Я понимаю, да, но это стоит изначально указывать в ответе, ибо новичок возьмет это за истину и будет дальше так и делать, не понимая, что нарушает тем самым MVVM. Вы можете глянуть мои ответы и увидите там например, что я пишу прям в конструкторе MainWindow, нарушая тем самым MVVM, но я всегда указываю "это не правильно, так не стоит делать" и вот было бы хорошо, если вы это тоже делали. – EvgeniyZ Aug 20 '20 at 13:05
  • @EvgeniyZ согласен, обновил хвост ответа. – aepot Aug 20 '20 at 13:11