You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中ViewModel属性传入Dispatcher后WriteableBitmap更新失效问题

问题分析与解决方案

首先,你当前遇到画面不显示的核心问题是C#引用类型的参数传递逻辑导致的:

在Utils.InjectBitmap方法中,Dest参数是按值传递的——哪怕你在方法内部创建了新的WriteableBitmap并赋值给Dest,这个修改只会改变方法内部的局部变量引用,完全不会同步到外部的VM.FrameDataNew。所以外部的VM.FrameDataNew始终保持null状态,自然无法渲染画面。

另外还有个隐藏的资源泄漏问题:你注释掉了Src.UnlockBits(data),这会导致Bitmap资源无法正常释放,必须恢复这行代码。


第一步:修复InjectBitmap方法

有两种可靠的方式解决参数传递的问题:

方式1:用ref关键字传递参数

修改方法签名,让Dest按引用传递,这样方法内部对Dest的引用修改会直接同步到外部变量:

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 (passed by reference)</param>
    public static void InjectBitmap(Bitmap Src, ref 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);
        try
        {
            Dest.Lock();
            try
            {
                // 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));
            }
            finally
            {
                Dest.Unlock();
            }
        }
        finally
        {
            Src.UnlockBits(data); // 必须解锁,避免资源泄漏
        }
    }

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

调用时需要加上ref关键字:

private void PipeLine_BitmapCaptured(object sender, Bitmap NewBitmap)
{
    Dispatcher.Invoke(() => 
    {
        Utils.InjectBitmap(NewBitmap, ref VM.FrameDataNew);
    });
}

方式2:让方法返回更新后的WriteableBitmap

如果不想用ref,可以让方法返回新的或已更新的WriteableBitmap,外部直接赋值:

public static class Utils
{
    public static WriteableBitmap 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);
        try
        {
            Dest.Lock();
            try
            {
                CopyMemory(Dest.BackBuffer, data.Scan0, (data.Stride * data.Height));
                Dest.AddDirtyRect(new Int32Rect(0, 0, data.Width, data.Height));
            }
            finally
            {
                Dest.Unlock();
            }
        }
        finally
        {
            Src.UnlockBits(data);
        }
        return Dest;
    }

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

调用方式:

private void PipeLine_BitmapCaptured(object sender, Bitmap NewBitmap)
{
    Dispatcher.Invoke(() => 
    {
        VM.FrameDataNew = Utils.InjectBitmap(NewBitmap, VM.FrameDataNew);
    });
}

第二步:实现多视频流的ObservableCollection方案

直接用ObservableCollection<WriteableBitmap>并不是最优解——因为WriteableBitmap本身不实现INotifyPropertyChanged,当你更新集合中某个项的内容时,UI无法自动感知到变化。更好的方案是创建一个封装WriteableBitmap的子ViewModel:

1. 创建子ViewModel类

public class VideoStreamViewModel : INotifyPropertyChanged
{
    private WriteableBitmap frameData;

    public WriteableBitmap FrameData
    {
        get => frameData;
        set
        {
            frameData = value;
            OnPropertyChanged(nameof(FrameData));
        }
    }

    // 可选:添加流名称、ID等标识属性,方便UI区分
    public string StreamName { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. 在主ViewModel中添加ObservableCollection

public class MainViewModel : INotifyPropertyChanged
{
    private ObservableCollection<VideoStreamViewModel> videoStreams;

    public ObservableCollection<VideoStreamViewModel> VideoStreams
    {
        get => videoStreams;
        set
        {
            videoStreams = value;
            OnPropertyChanged(nameof(VideoStreams));
        }
    }

    public MainViewModel()
    {
        VideoStreams = new ObservableCollection<VideoStreamViewModel>();
        // 初始化时可按需添加空流项,或后续根据管线动态添加
    }

    // 实现INotifyPropertyChanged接口
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

3. 绑定UI到集合

在XAML中用ItemsControl或ListView显示多个视频流,每个项绑定到VideoStreamViewModel.FrameData:

<ItemsControl ItemsSource="{Binding VideoStreams}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Margin="10">
                <TextBlock Text="{Binding StreamName}" Margin="0 0 0 5" />
                <Image Source="{Binding FrameData}" Stretch="Uniform" Width="320" Height="240" />
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel /> <!-- 用WrapPanel实现流式布局,也可替换为Grid等自定义布局 -->
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

4. 处理多管线的帧更新

每个管线的BitmapCaptured事件需要知道对应更新哪个VideoStreamViewModel实例。可以通过事件参数传递流ID,或者在创建管线时将ViewModel关联到管线的Tag属性:

// 创建新管线时,先添加对应的ViewModel到集合
var camera1Vm = new VideoStreamViewModel { StreamName = "Camera 1" };
VM.VideoStreams.Add(camera1Vm);

// 创建管线并绑定事件,把ViewModel存入Tag属性
var pipeline1 = new CustomGstreamerPipeline();
pipeline1.Tag = camera1Vm;
pipeline1.BitmapCaptured += MultiPipeline_BitmapCaptured;

// 多流的事件处理方法
private void MultiPipeline_BitmapCaptured(object sender, Bitmap NewBitmap)
{
    var pipeline = sender as CustomGstreamerPipeline;
    var targetVm = pipeline.Tag as VideoStreamViewModel;
    if (targetVm == null) return;

    Dispatcher.Invoke(() => 
    {
        // 用修复后的InjectBitmap方法更新
        Utils.InjectBitmap(NewBitmap, ref targetVm.FrameData);
        // 或用返回值方式:
        // targetVm.FrameData = Utils.InjectBitmap(NewBitmap, targetVm.FrameData);
    });
}

这样既解决了当前单流的显示问题,又为多流场景提供了符合MVVM规范、可扩展的高效实现方案。

内容的提问来源于stack exchange,提问作者MrVanx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:17:57