C#:将ViewModel的属性传递到Dispatcher

3

我有一个方法,它会更新我的视图模型(VM)中的属性WriteableBitmap(FrameDataNew)的内容:

    public WriteableBitmap FrameDataNew
    {
        get { return frameDataNew; }
        set
        {
            frameDataNew = value;
            OnProptertyChanged("FrameDataNew");
        }
    }
    private WriteableBitmap frameDataNew = null; 

当我从自己编写的gstreamer类中收到一个新的位图时,我会更新FrameDataNew以显示最新的帧。窗口是一个简单的Image控件,其源绑定到FrameDataNew。
以下代码在我的事件处理程序中很好地实现了这一点:
    /// <summary>
    /// BitmapCaptured event handler for when a new video frame is received from the IP source
    /// </summary>
    /// <param name="sender">The originating source of the event</param>
    /// <param name="NewBitmap">The new frame in Bitmap form</param>
    private void PipeLine_BitmapCaptured(object sender, Bitmap NewBitmap)
    {
        // render to the screen
        Dispatcher.Invoke(() =>
        {
            // check the existence of the WriteableBitmap and also the dimensions, create a new one if required
            if ((VM.FrameDataNew == null) || (VM.FrameDataNew.Width != NewBitmap.Width) || (VM.FrameDataNew.Height != NewBitmap.Height))
                VM.FrameDataNew = new WriteableBitmap(NewBitmap.Width, NewBitmap.Height, NewBitmap.HorizontalResolution, NewBitmap.VerticalResolution, PixelFormats.Bgr24, null);

            // lock the bitmap data so we can use it
            BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

            // lock the backbuffer of the WritebaleBitmap so we can modify it
            VM.FrameDataNew.Lock();

            // Copy the bitmap's data directly to the on-screen buffers
            CopyMemory(VM.FrameDataNew.BackBuffer, data.Scan0, (data.Stride * data.Height));

            // Moves the back buffer to the front.
            VM.FrameDataNew.AddDirtyRect(new Int32Rect(0, 0, data.Width, data.Height));

            // unlock the back buffer again
            VM.FrameDataNew.Unlock();

            // 
            //NewBitmap.UnlockBits(data);
        });
    }

    [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]
    public static extern void CopyMemory(IntPtr Destination, IntPtr Source, int Length);

现在,我想升级我的程序以处理多个管道和可写位图,这样我就可以显示几个视频源。我做的第一件事是创建一个静态实用程序类,这样我就可以传递新位图(NewBitmap)和要更新的可写位图(VM.FrameDataNew)。当新帧到达时,我按需要调用它:
实用程序类(只涉及我的问题的代码):
    public static class Utils
{
    /// <summary>
    /// Inject the source Bitmap into the Destination WriteableBitmap
    /// </summary>
    /// <param name="Src">The source Bitmap</param>
    /// <param name="Dest">The destination WriteableBitmap</param>
    public static void InjectBitmap(Bitmap Src, WriteableBitmap Dest)
    {
        if ((Dest == null) || (Dest.Width != Src.Width) || (Dest.Height != Src.Height))
            Dest = new WriteableBitmap(Src.Width, Src.Height, Src.HorizontalResolution, Src.VerticalResolution, PixelFormats.Bgr24, null);

        BitmapData data = Src.LockBits(new Rectangle(0, 0, Src.Width, Src.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
        Dest.Lock();

        // Copy the bitmap's data directly to the on-screen buffers
        CopyMemory(Dest.BackBuffer, data.Scan0, (data.Stride * data.Height));

        // Moves the back buffer to the front.
        Dest.AddDirtyRect(new Int32Rect(0, 0, data.Width, data.Height));
        Dest.Unlock();

        // 
        Src.UnlockBits(data);
    }

    [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]
    public static extern void CopyMemory(IntPtr Destination, IntPtr Source, int Length);
}

...以及我称之为的方式:

/// <summary>
    /// BitmapCaptured event handler for when a new video frame is received from the IP source
    /// </summary>
    /// <param name="sender">The originating source of the event</param>
    /// <param name="NewBitmap">The new frame in Bitmap form</param>
    private void PipeLine_BitmapCaptured(object sender, Bitmap NewBitmap)
    {
        // render to the screen
        Dispatcher.Invoke(() =>
        {
            Utils.InjectBitmap(NewBitmap, VM.FrameDataNew);
        });
    }

图像不再显示在屏幕上。通过代码一步步调试发现,每次调用InjectBitmap()时,目标WriteableBitmap为空?在第一个if语句中,代码创建了一个新的WriteableBitmap,但VM.FrameDataNew仍为空?
显然,我对此已经到了极限,所以非常感谢您的帮助。
编辑:目标是拥有一个可观察的WriteableBitmaps集合,以便可以高效地处理多个图像。

你自己使用自定义视频接收器实现了BitmapCaptured吗?能否请您发布一下代码?谢谢! - superware
1个回答

1
原因很简单:您没有将新创建的对象分配给FrameDataNew属性,因此其值仍为null
请不要忘记,在C#中,引用类型实例是按引用传递的。
因此,在您的方法中,您正在执行以下操作:
void InjectBitmap(Bitmap Src, WriteableBitmap Dest)
{
    if (Dest == null)
        Dest = new WriteableBitmap(/* ... */);
}

但是Dest只是您方法内部的一个本地变量 - 方法的参数可以被视为方法的本地变量。
因此,您将新创建的实例分配给一个本地变量,当方法返回时,该本地变量将被丢失。
在这里需要的是一个ref参数:
void InjectBitmap(Bitmap src, ref WriteableBitmap dest)
{
    if (dest == null)
        dest = new WriteableBitmap(/* ... */);
}

请注意,我已更改参数名称的大小写以匹配C#风格指南。
但是,对于属性,这种方法不起作用,因此如果FrameDataNew是属性,则InjectBitmap(NewBitmap,VM.FrameDataNew)将无法编译。
您可以将FrameDataNew更改为字段,但拥有非私有可变字段是一个坏主意。
您可以使用临时本地变量,但这样做有些丑陋:
var copy = VM.FrameDataNew;
InjectBitmap(NewBitmap, ref copy);
if (copy != VM.FrameDataNew)
    VM.FrameDataNew = copy;

我建议你重新考虑设计,这样就不需要那么多东西了。
顺便说一下,在多线程环境中多次调用Dispatcher.Invoke(特别是如果被调用的方法很慢,而你的确实很慢)会降低应用程序的性能,并使用户界面无响应。

谢谢您的评论,我想问题已经得到解答了 :) 我已经开始推进项目了,而不是使用WriteableBitmap,我传递了一个ObservableCollection<WriteableBitmap>和通道号码,这种方法运行良好,所以我可能会在不需要提问的情况下达到那个阶段,这很讽刺。关于性能方面,当我拥有所需的通道数量时,我会关注响应性并在那里进行处理。再次感谢。 - MrVanx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接