简体   繁体   中英

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.

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.

在此输入图像描述

   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.

You'll want to do data-binding with your "lines" instead of all the work you're doing here. This is not winforms. You bind in WPF, and binding does all this work for you.

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

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

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

Then add a 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.

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:

<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. That is the correct approach when you need to show several Items in WPF, no matter what these items are. Actually, in WPF, all UI elements capable of showing several Items (such as ListBox , ComboBox , Menu , etc) are derived from ItemsControl .
  • I'm in NO WAY manipulating UI elements in code . That is completely unnecessary most of the time in WPF. again, UI is Not Data. Data is Data. UI is UI. , therefore you must not treat UI elements as if they were data, because they're not.
  • Notice that I'm using an ObservableCollection<T> to store these items. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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