简体   繁体   English

更新wpf中的UI元素

[英]Update UI element in wpf

I am trying to animate bubble sort algorithm in wpf .For the same I have written following code.Code is compiling.The problem is that when I clicked on sort button it's not updating the UI element.I am facing issue in swapData method. 我试图在wpf中设置动画冒号排序算法。对于我写的以下代码。编码正在编译。问题是,当我点击排序按钮时,它不会更新UI元素。我在swapData方法中遇到问题。

Edit: 编辑:

When I click the sort button UI freezes.but I want when I click on sort button it's show the swapping of line. 当我单击排序按钮时,UI冻结。但是我想要在单击排序按钮时显示换行。

在此输入图像描述

   using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections;
using System.Threading;
using System.ComponentModel;
using System.Windows.Threading;

namespace Sorting
{

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public struct SwapIndex
        {
            public int i; public int j;
        };
        delegate void UIswap(int i, int j);
        const int scale = 4;
        const int size = 50;
        Int32[] data = new Int32[size];
        bool Working = false;
        Line[] lines = new Line[size];
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            Draw();
        }
        private void Draw()
        {
            canvas1.Children.Clear();
            for (int i = 0; i < data.Length; i++)
            {
                data[i] = i;
                lines[i] = new Line()
                {
                    X1 = 0,
                    Y1 = i * scale,
                    X2 = i * scale,
                    Y2 = i * scale,
                    StrokeThickness = 2,
                    Stroke = new SolidColorBrush(Colors.Black)
                };
                canvas1.Children.Add(lines[i]);
            }
        }

        private void Sort_Click(object sender, RoutedEventArgs e)
        {
            if (Working) return;
            Working = true;
            Thread T1 = new Thread(new ThreadStart(BubbleSimple));
            T1.Start();

        }
        void BubbleSimple()
        {
            bool flag = false;
            do
            {
                flag = false;
                for (int i = 0; i < data.Length - 1; i++)
                {
                    if (data[i] > data[i + 1])
                    {
                        flag = true;
                        swapData(i, i + 1);
                    }
                }
            } while (flag);
            Working = false;
        }

        private void swapData(int i, int j)
        {

            UIswap swap = (i1, j1) =>
                {
                    double temp;
                    temp = lines[i1].X2;
                    lines[i1].X2 = lines[j1].X2;
                    lines[j1].X2 = temp;
                };
            canvas1.Dispatcher.BeginInvoke(swap, new object[] { i, j });
        }
        void Randomize(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = (BackgroundWorker)sender;
            Random R = new Random();
            for (int i = 0; i < data.Length; i++)
            {
                int j = R.Next(data.Length);
                bw.ReportProgress(1, new SwapIndex() { i = i, j = j });
            }
        }
        void SwapLine(object sender, ProgressChangedEventArgs e)
        {
            int i = ((SwapIndex)e.UserState).i;
            int j = ((SwapIndex)e.UserState).j;
            int t = data[i];
            data[i] = data[j];
            data[j] = t;

            double temp;
            temp = lines[i].X2;
            lines[i].X2 = lines[j].X2;
            lines[j].X2 = temp;
        }
        private void Suffle_Click(object sender, RoutedEventArgs e)
        {
            if (Working) return;
            Working = true;
            BackgroundWorker bw = new BackgroundWorker();
            bw.WorkerReportsProgress = true;
            bw.WorkerSupportsCancellation = false;
            bw.DoWork += new DoWorkEventHandler(Randomize);
            bw.ProgressChanged += new ProgressChangedEventHandler(SwapLine);
            bw.RunWorkerCompleted += delegate(object s1, RunWorkerCompletedEventArgs e1)
            {
                Working = false;
            };
            bw.RunWorkerAsync();
        }

    }
}

To answer this question we have to go back to WPF 101. 要回答这个问题,我们必须回到WPF 101。

You'll want to do data-binding with your "lines" instead of all the work you're doing here. 你想要用你的“线”进行数据绑定,而不是你在这里做的所有工作。 This is not winforms. 这不是winforms。 You bind in WPF, and binding does all this work for you. 您在WPF中进行绑定,绑定将为您完成所有这些工作

First, you'll need to use your data objects instead of lines. 首先,您需要使用数据对象而不是行。

public class DataLine
{
    private const double _Scale = 4.0;
    public double Length { get; set; }
    public double DisplayLength { get { return Length * _Scale; } }
}

Then add an ObservableCollection to your window class 然后在窗口类中添加一个ObservableCollection

public ObservableCollection<DataLine> _Data = new ObservableCollection<DataLine>();
public ObservableCollection<DataLine> Data
{
    get { return _Data; }
}

Then you'll bind ItemsSource on a control in your Window's xaml 然后,将ItemsSource绑定到Window的xaml中的控件上

<ItemsControl Grid.Row="2" ItemsSource="{Binding Data}"/>

Then add a DataTemplate 然后添加一个DataTemplate

<ItemsControl Grid.Row="2" ItemsSource="{Binding Data}">
    <ItemsControl.Resources>
        <DataTemplate DataType="{x:Type local:DataLine}">
            <Line X1="0" X2="{Binding DisplayLength}" Y1="{Binding DisplayLength}" Y2="{Binding Length}"/>
        </DataTemplate>
    </ItemsControl.Resources>
</ItemsControl>

That governs the UI. 这决定了用户界面。 Now all you need to do to rearrange your lines is to rearrange the Data array in your class. 现在,重新排列行所需要做的就是重新排列类中的Data数组。

private void swapData(int i, int j)
{
    int nMax = Math.Max(i, j);
    int nMin = Math.Min(i, j);

    DataLine tempMax = Data[nMax];
    DataLine tempMin = Data[nMin];


    Action swap = () =>
    {
        Data.RemoveAt(nMax);
        Data.RemoveAt(nMin);
        Data.Insert(nMin, tempMax);
        Data.Insert(nMax, tempMin);
    };

    Dispatcher.Invoke(swap, null);
}

Now just put a wait between each swap and do your sort in a separate thread. 现在,只需在每个交换之间进行等待,然后在单独的线程中进行排序即可。

As others have mentioned data binding would be better, but to show you where you have gone wrong without completely re-writing your code this is what I came up with: 正如其他人提到的那样,数据绑定会更好,但是为了向您展示您出了问题的地方而又没有完全重新编写代码,这就是我想到的:

public partial class MainWindow : Window
{
    public struct SwapIndex
    {
        public int i; public int j;
    };
    delegate void UIswap(int i, int j);
    const int scale = 4;
    const int size = 50;
    Int32[] data = new Int32[size];
    bool Working = false;
    Line[] lines = new Line[size];
    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        Draw();
    }
    private void Draw()
    {
        canvas1.Children.Clear();
        for (int i = 0; i < data.Length; i++)
        {
            data[i] = i;
            lines[i] = new Line()
            {
                X1 = 0,
                Y1 = i * scale,
                X2 = i * scale,
                Y2 = i * scale,
                StrokeThickness = 2,
                Stroke = new SolidColorBrush(Colors.Black)
            };
            canvas1.Children.Add(lines[i]);
        }
    }

    private void Sort_Click(object sender, RoutedEventArgs e)
    {
        if (Working) return;
        Working = true;
        Thread T1 = new Thread(new ThreadStart(BubbleSimple));
        T1.Start();

    }
    void BubbleSimple()
    {
        bool flag = false;
        do
        {
            flag = false;
            for (int i = 0; i < data.Length - 1; i++)
            {
                if (data[i] > data[i + 1])
                {
                    flag = true;
                    swapData(i, i + 1);
                }

                Thread.Sleep(10);
            }
        } while (flag);
        Working = false;
    }

    private void swapData(int i, int j)
    {
        var temp = data[i];
        data[i] = data[j];
        data[j] = temp;

        UIswap swap = (i1, j1) =>
        {
            var tempd = lines[i1].X2;
            lines[i1].X2 = lines[j1].X2;
            lines[j1].X2 = tempd;
        };

        canvas1.Dispatcher.BeginInvoke(swap, new object[] { i, j });
    }

    void Randomize(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker bw = (BackgroundWorker)sender;
        Random R = new Random();
        for (int i = 0; i < data.Length; i++)
        {
            int j = R.Next(data.Length);
            bw.ReportProgress(1, new SwapIndex() { i = i, j = j });
        }
    }
    void SwapLine(object sender, ProgressChangedEventArgs e)
    {
        int i = ((SwapIndex)e.UserState).i;
        int j = ((SwapIndex)e.UserState).j;
        int t = data[i];
        data[i] = data[j];
        data[j] = t;

        double temp;
        temp = lines[i].X2;
        lines[i].X2 = lines[j].X2;
        lines[j].X2 = temp;
    }
    private void Suffle_Click(object sender, RoutedEventArgs e)
    {
        if (Working) return;
        Working = true;
        BackgroundWorker bw = new BackgroundWorker();
        bw.WorkerReportsProgress = true;
        bw.WorkerSupportsCancellation = false;
        bw.DoWork += new DoWorkEventHandler(Randomize);
        bw.ProgressChanged += new ProgressChangedEventHandler(SwapLine);
        bw.RunWorkerCompleted += delegate(object s1, RunWorkerCompletedEventArgs e1)
        {
            Working = false;
        };
        bw.RunWorkerAsync();
    }
}

Your biggest problem was the algorithm was swapping the lines, but not swaping the data, thus it was in an infinite loop. 你最大的问题是算法交换了行,但没有交换数据,因此它处于无限循环中。

You need to understand the way UI is handled in WPF, which is something called MVVM: 您需要了解在WPF中处理UI的方式,这称为MVVM:

<Window x:Class="MiscSamples.WrongCode"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WrongCode" Height="300" Width="300">
    <DockPanel>
        <Button Click="Sort" Content="Sort" DockPanel.Dock="Top"/>
        <Button Click="Shuffle" Content="Shuffle" DockPanel.Dock="Top"/>
        <ItemsControl ItemsSource="{Binding}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Line X1="0" Y1="0" X2="{Binding Length}" Y2="0"
                          Stroke="Black" StrokeThickness="2" Margin="0,2,0,2"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </DockPanel>
</Window>

Code Behind: 代码背后:

public partial class WrongCode : Window
    {
        public ObservableCollection<LineModel> Lines { get; set; }

        public WrongCode()
        {
            InitializeComponent();
            Lines = new ObservableCollection<LineModel>();
            DataContext = Lines;
        }

        private void Sort(object sender, RoutedEventArgs e)
        {
            SortTimer = new Timer(x => SortItem(), null, 0, 100);
        }

        private void SortItem()
        {
            //Implement your sort algorithm here by
            //Modifying the ObservableCollection in this way:
            //Lines.Move(index1, index2);

            //This example is just moving the lines randomly without any sort order
            var index1 = rnd.Next(0, Lines.Count - 1);
            var index2 = rnd.Next(0, Lines.Count - 1);

            Dispatcher.BeginInvoke((Action) (() => Lines.Move(index1, index2)));
        }

        public static System.Threading.Timer SortTimer;
        public static Random rnd = new Random();

        private void Shuffle(object sender, RoutedEventArgs e)
        {
            if (SortTimer != null)
                SortTimer.Dispose();

            Lines.Clear();

            Enumerable.Range(0, rnd.Next(50, 60))
                      .Select(x => new LineModel()
                          {
                              Length = rnd.Next(1, 100)
                          })
                      .ToList()
                      .ForEach(Lines.Add);

        }
    }

    public class LineModel
    {
        public int Length { get; set; }
    }

Result: 结果:

在此输入图像描述

Important points to note here: 这里要注意的重要事项:

  • I'm using an ItemsControl to "draw" the items on-screen. 我正在使用ItemsControl来“绘制”屏幕上的项目。 That is the correct approach when you need to show several Items in WPF, no matter what these items are. 当你需要在WPF中显示多个Items时,这是正确的方法,无论这些项目是什么。 Actually, in WPF, all UI elements capable of showing several Items (such as ListBox , ComboBox , Menu , etc) are derived from ItemsControl . 实际上,在WPF中,所有能够显示多个项目的UI元素(例如ListBoxComboBoxMenu等)都是从ItemsControl派生的。
  • I'm in NO WAY manipulating UI elements in code . 我没有在代码中操纵UI元素 That is completely unnecessary most of the time in WPF. 在WPF中,大多数时候这完全是不必要的。 again, UI is Not Data. 同样, UI不是数据。 Data is Data. 数据是数据。 UI is UI. UI是UI。 , therefore you must not treat UI elements as if they were data, because they're not. 因此,您不能将UI元素视为数据,因为它们不是。
  • Notice that I'm using an ObservableCollection<T> to store these items. 请注意,我正在使用ObservableCollection<T>来存储这些项目。 This is a special type of collection that notifies whenever an item is added/removed/moved within itself. 这是一种特殊类型的集合,可以在项目本身添加/删除/移动时通知。 The WPF binding framework listens for these events and updates the UI automatically. WPF绑定框架侦听这些事件并自动更新UI。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM