Running a WPF control in another thread

后端 未结 2 1770
温柔的废话
温柔的废话 2020-12-08 03:31

I am using a visual control in my project that is from a library that I do not have the source to.
It takes too long to update (200ms, roughly) for good UI responsivenes

2条回答
  •  无人及你
    2020-12-08 03:31

    Well, I have a method that works - but it may well not be the most elegant..

    I have a window that contains my third party (slow-rendering) control in the XAML.

    public partial class MapWindow : Window
    {
        private ConcurrentQueue _mapCommandQueue;
        private HwndSource _source;
    
        // ...
    
    }
    

    My main (UI) thread contructs and starts this window on a thread:

    _leftTopThread = new Thread(() =>
    {
       _topLeftMap = new MapWindow()
       {
          WindowStartupLocation = WindowStartupLocation.Manual,
          CommandQueue = _leftMapCommendQueue,
       };
    
        _topLeftMap.Show();
        System.Windows.Threading.Dispatcher.Run();
    
    });
    
    _leftTopThread.SetApartmentState(ApartmentState.STA);
    _leftTopThread.IsBackground = true;
    _leftTopThread.Name = "LeftTop";
    _leftTopThread.Start();
    

    I then get a handle to the window in the thread (after it has initialised):

    private IntPtr LeftHandMapWindowHandle
    {
        get
        {
            if (_leftHandMapWindowHandle == IntPtr.Zero)
            {
                if (!_topLeftMap.Dispatcher.CheckAccess())
                {
                    _leftHandMapWindowHandle = (IntPtr)_topLeftMap.Dispatcher.Invoke(
                      new Func(() => new WindowInteropHelper(_topLeftMap).Handle)
                    );
                }
                else
                {
                    _leftHandMapWindowHandle = new WindowInteropHelper(_topLeftMap).Handle;
                }
            }
            return _leftHandMapWindowHandle;
        }
    }
    

    .. and after putting a command onto the thread-safe queue that is shared with the threaded window:

    var command = new MapCommand(MapCommand.CommandType.AircraftLocation, new object[] {RandomLatLon});
    _leftMapCommendQueue.Enqueue(command);
    

    .. I let it know it can check the queue:

    PostMessage(LeftHandMapWindowHandle, MapWindow.WmCustomCheckForCommandsInQueue, IntPtr.Zero, IntPtr.Zero);
    

    The window can receive my message because it has hooked into the window messages:

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
    
        _source = PresentationSource.FromVisual(this) as HwndSource;
        if (_source != null) _source.AddHook(WndProc);
    }
    

    ..which it then can check:

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) // 
    {
        // Handle messages...
        var result = IntPtr.Zero;
    
        switch (msg)
        {
            case WmCustomCheckForCommandsInQueue:
                CheckForNewTasks();
                break;
    
        }
        return result;
    }
    

    ..and then execute on the thread!

    private void CheckForNewTasks()
    {
        MapCommand newCommand;
        while (_mapCommandQueue.TryDequeue(out newCommand))
        {
            switch (newCommand.Type)
            {
                case MapCommand.CommandType.AircraftLocation:
                    SetAircraftLocation((LatLon)newCommand.Arguments[0]);
                    break;
    
                default:
                    Console.WriteLine(String.Format("Unknown command '0x{0}'for window", newCommand.Type));
                    break;
            }
        }
    }
    

    Easy as that.. :)

提交回复
热议问题