Question

I am trying to update a writeablebitmap by using multithreading, however I do not manage to get the ProgressChanged Event trigger any change at all to the UI. It is although on the verge, since once the RunWorkerCompleted event is called the UI updates. Have I miss understood the purpose of the ProgressChanged event or what is the matter?

Code for progresschanged is as follows:

    private void BgWorkerMap_ProgressChanged_Handler(object sender, ProgressChangedEventArgs progressChangedArgs)
    {
        TestBitmap[7].Wb.AddDirtyRect(new Int32Rect(args[7].BgWorkerProperties.ProccessedX, args[7].BgWorkerProperties.ProccessedY, 256, 256));
        TestBitmap[7].Wb.Unlock();
        Src = TestBitmap[7].Wb;
        TestBitmap[7].Wb.Lock();
    }

Here is the full code Im using. If you try it out you will see at after 3 secs the UI will be updated (Added a thread sleep 1000ms / loop)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Testmultithredabitmap
{


public partial class MainWindow : INotifyPropertyChanged
{
    private readonly BackgroundWorker worker;
    private readonly Random random = new Random();
    private WriteableBitmap src;
    public WriteableBitmap Src
    {
        get { return src; }
        set
        {
            if (Equals(value, src)) return;
            src = value;
            NotifyPropertyChanged("Src");
        }
    }

    private class WorkerArgs
    {
        public int Width { get; set; }
        public int Height { get; set; }
        public IntPtr BackBuffer { get; set; }
    }

    WorldMapFloor[] TestBitmap = new WorldMapFloor[14];

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        Src = new WriteableBitmap(256, 256, 96, 96, PixelFormats.Bgr32, null);

        for (int i = 0; i < TestBitmap.Length; i++)
        {
            TestBitmap[i] = new WorldMapFloor(i);

            TestBitmap[i].Wb.Lock();
        }

        worker = new BackgroundWorker();

        worker.WorkerReportsProgress = true;

        worker.DoWork += WorkerOnDoWork;
        worker.ProgressChanged += BgWorkerMap_ProgressChanged_Handler;
        worker.RunWorkerCompleted += WorkerOnRunWorkerCompleted;
        worker.RunWorkerAsync(TestBitmap);

    }

    private void WorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
    {
        TestBitmap[7].Wb.AddDirtyRect(new Int32Rect(0, 0, 256 * 3, 256));
        TestBitmap[7].Wb.Unlock();
        Src = TestBitmap[7].Wb;
        //TestBitmap[7].Wb.Lock();
    }

    private void WorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
    {
        WorldMapFloor[] args = (WorldMapFloor[])doWorkEventArgs.Argument;

        var width = args[7].BgWorkerProperties.Stride;

        int index = 0;

        while (true)
        {
            int startXCord = (index) * 256;
            int startYCord = (0) * 256;

            int filrStartIndexInBuffer = startXCord + startYCord * 256;

            args[7].BgWorkerProperties.ProccessedX = startXCord;
            args[7].BgWorkerProperties.ProccessedY = startYCord;

            Thread.Sleep(1000);

            unsafe
            {
                var buffer = (int*)args[7].BgWorkerProperties.pBackBuffer;
                for (var x = 0; x < 256; ++x)
                {
                    for (var y = 0; y < 256; ++y)
                    {
                        buffer[filrStartIndexInBuffer + x + y * (width / 4)] = random.Next();
                    }
                }
            }
            worker.ReportProgress(0, args);
            ++index;
            if (index == 3) break;
        }
    }

    private void BgWorkerMap_ProgressChanged_Handler(object sender, ProgressChangedEventArgs progressChangedArgs)
    {
        TestBitmap[7].Wb.AddDirtyRect(new Int32Rect(args[7].BgWorkerProperties.ProccessedX, args[7].BgWorkerProperties.ProccessedY, 256, 256));
        TestBitmap[7].Wb.Unlock();
        Src = TestBitmap[7].Wb;
        TestBitmap[7].Wb.Lock();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class BgWorkerMapProperties
    {
        public int ProccessedX;
        public int ProccessedY;

        public IntPtr pBackBuffer;
        public int Stride;


        public BgWorkerMapProperties(WriteableBitmap wb, int floorZ)
        {
            // Get a pointer to the back buffer. 
            pBackBuffer = wb.BackBuffer;
            Stride = wb.BackBufferStride;


        }
    }

    public class WorldMapFloor
    {
        public WriteableBitmap Wb = new WriteableBitmap(256 * 8, 256 * 8, 96, 96, PixelFormats.Bgr32, null);

        public BgWorkerMapProperties BgWorkerProperties;

        public string[] Files = null;

        public WorldMapFloor(int floorZ)
        {
            BgWorkerProperties = new BgWorkerMapProperties(Wb, floorZ);
        }

    }
}
}

Thanks on advance, from a desperate WPF programmer.

Was it helpful?

Solution

By Henk Holtermans comment I managed to make it update properly, the whole reason seems to be that you shall only update 1 single bitmap, but since I want to store different bitmaps as an chache and quickly swap them. All I had to do was to copy the backbuffer data to the "Main bitmap"

So this is how the progresschange event should look like:

private void BgWorkerMap_ProgressChanged_Handler(object sender, ProgressChangedEventArgs progressChangedArgs)
{
        Src.Lock();

        int size = Src.BackBufferStride * Src.PixelHeight;

        // Copy the bitmap's data directly to the on-screen buffers
        // Method is DLL imported from kernel32.dll
        CopyMemory(Src.BackBuffer, TestBitmap[7].Wb.BackBuffer, size);

        Src.AddDirtyRect(new Int32Rect(0, 0, 256 * 3, 256));

        Src.Unlock();
}

You can, in my case, even remove the workercomplete event, since nothing has be done after the bitmaps data is rendered on the UI.

OTHER TIPS

To me it doesn't seem like you are ever reporting any other progress percentage but 0. When the worker completes it's job, it will report 100%.

You hare to call worker.ReportProgress(percentage) at different points in your flow. You can read more here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top