简体   繁体   中英

How to use the ISynchronizeInvoke interface in WPF?

I have a component like this and can't change it:

public sealed class UFScannerManager
{
    public UFScannerManager(ISynchronizeInvoke synInvoke);
    public ScannerList Scanners { get; }
    public event UFS_SCANNER_PROC ScannerEvent;

    public UFS_STATUS Init();
    public UFS_STATUS Uninit();
    public UFS_STATUS Update();

    [DefaultMember("Item")]
    public sealed class ScannerList
    {
        public ScannerList(UFScannerManager Owner);

        public UFScanner this[int Index] { get; }
        public UFScanner this[string ScannerID] { get; }
        public UFScanner this[IntPtr ScannerHandle] { get; }

        public int Count { get; }
    }
}

I want to create an instance of the component like this: UFScannerManager(this) , but in WPF I cannot call pass this as an argument. Here this means the current window form object, the constructor required a ISynchronizeInvoke sysInvoke parameter. So when passing this , the scanner can be initialized properly in a Windows Form applications. No need to worry about ISynchronizeInvoke interface.

UFS_STATUS ufs_res;
UFScannerManager ScannerManager;
int nScannerNumber;
ScannerManager = new UFScannerManager(this);
ufs_res = ScannerManager.Init();
nScannerNumber = ScannerManager.Scanners.Count;

However, this code does not work in WPF. The problem is this line. The bit it does not like is this .

ScannerManager = new UFScannerManager(this);

When I try to build, I get an error:

Argument 1: cannot convert from 'win_myapp' to 'System.ComponentModel.ISynchronizeInvoke'

WPF doesn't provide an ISynchronizeInvoke implementation like the System.Windows.Forms.Form class does. So you need to create one.

Fortunately, the WPF's Dispatcher class provides all the needed means for that implementation. You just have to create wrappers/adapters for the Dispatcher and for the DispatcherOperation .

I can give you an idea how to do this. Please note that this code should not be used in a production environment 'as is', because it's simplified and lacks the exception handling.

class DispatcherSynchronizeInvoke : ISynchronizeInvoke
{
    private readonly Dispatcher dispatcher;

    public DispatcherSynchronizeInvoke(Dispatcher dispatcher)
    {
        this.dispatcher = dispatcher;
    }

    public IAsyncResult BeginInvoke(Delegate method, object[] args)
    {
        // Obtaining a DispatcherOperation instance
        // and wrapping it with our proxy class
        return new DispatcherAsyncResult(
            this.dispatcher.BeginInvoke(method, DispatcherPriority.Normal, args));
    }

    public object EndInvoke(IAsyncResult result)
    {
        DispatcherAsyncResult dispatcherResult = result as DispatcherAsyncResult;
        dispatcherResult.Operation.Wait();
        return dispatcherResult.Operation.Result;
    }

    public object Invoke(Delegate method, object[] args)
    {
        return dispatcher.Invoke(method, DispatcherPriority.Normal, args);
    }

    public bool InvokeRequired => !this.dispatcher.CheckAccess();

    // We also could use the DispatcherOperation.Task directly
    private class DispatcherAsyncResult : IAsyncResult
    {      
        private readonly IAsyncResult result;

        public DispatcherAsyncResult(DispatcherOperation operation)
        {
            this.Operation = operation;
            this.result = operation.Task;
        }

        public DispatcherOperation Operation { get; }

        public bool IsCompleted => this.result.IsCompleted;
        public WaitHandle AsyncWaitHandle => this.result.AsyncWaitHandle;
        public object AsyncState => this.result.AsyncState;
        public bool CompletedSynchronously => this.result.CompletedSynchronously;
    }
}

Using this custom ISynchronizeInvoke implementation, you can instantiate your classes:

// Assuming you're calling this inside of a DispatcherObject, e.g. a Window
new UFScannerManager(new DispatcherSynchronizeInvoke(this.Dispatcher));

Building on @dymanoid's example. Making this into an extension method is preferable.

public static class WPFExtensions
{
    public static System.ComponentModel.ISynchronizeInvoke ISynchronizeInvoke
        (this System.Windows.Threading.DispatcherObject dispatcher)
    {
        return new DispatcherSynchronizeInvoke(dispatcher.Dispatcher);
    }
}

class DispatcherSynchronizeInvoke : System.ComponentModel.ISynchronizeInvoke
{
    // see dymanoid's answer, you do need one additional line in the internal
    // private class DispatcherAsyncResult : IAsyncResult
    // this >>
    //   WaitHandle IAsyncResult.AsyncWaitHandle => this.result.AsyncWaitHandle;
}

This works great with the timer class, such that you don't have to marshal the elasped event. From a window you can do:

var timer = new Timer(TimeSpan.FromSeconds(10).TotalMilliseconds) 
{ 
    SynchronizingObject = this.ISynchronizeInvoke() 
};
timer.Elapsed += timer_Elapsed;

Then the timer's elapsed event is on the window's thread.

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