【问题标题】:Asynchronously setting Image.Source with NotifyPropertyChange使用 NotifyPropertyChange 异步设置 Image.Source
【发布时间】:2017-01-19 16:19:21
【问题描述】:

我有一个通过一些滑块修改的图像,其值被发送到程序上(重新)生成位图源的方法。

目前事情很麻烦,因为滑块的移动是滞后的。我相信这是由于 Update 方法的阻塞性质。

我试图“让事情更加异步”,但显然没有成功,因为滑块移动仍然滞后。

我在下面发布我的代码,问题是:我做错了什么,在 WPF 中异步更新程序生成的图像的正确方法是什么?

需要注意的一点是,IDE 抱怨没有等待 Update() 调用,“在调用完成之前继续执行”,但我不确定这意味着什么,或者我应该怎么做。


MainWindow.xaml

<Window x:Class="Miotec.FranjasSenoidais.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Miotec.FranjasSenoidais"
        xmlns:toolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
        mc:Ignorable="d"
        Title="MainWindow">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <Grid x:Name="Imagem" Grid.Row="0" Grid.Column="0">
            <Image x:Name="franjas" Width="800" Height="600"
                   Source="{Binding ImageSource}"/>
        </Grid>

        <Grid x:Name="SliderLateral" Grid.Row="0" Grid.Column="1">
            <toolkit:RangeSlider Orientation="Vertical"
                                 Maximum="1" Minimum="0" Step="0.01"
                                 HigherValue="{Binding Maximo}" 
                                 LowerValue="{Binding Minimo}"/>
        </Grid>

        <Grid x:Name="SliderEspessuraFranja" Grid.Row="1" Grid.Column="0">
            <Slider Orientation="Horizontal" Value="{Binding Espessura}"
                    Minimum="4" Maximum="16"/>
        </Grid>
    </Grid>
</Window>

MainWindow.xaml.cs(为简洁起见,DataContext 在后面的代码中)

using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;

namespace Miotec.FranjasSenoidais
{

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public double Maximo
        {
            get { return _maximo; }
            set
            {
                _maximo = value;
                RaisePropertyChanged("Maximo");
                Update();
            }
        }

        double _maximo = 1;

        public double Minimo
        {
            get { return _minimo; }
            set
            {
                _minimo = value;
                RaisePropertyChanged("Minimo");
                Update();
            }
        }
        double _minimo = 0;

        public int Espessura
        {
            get { return _espessura; }
            set
            {
                _espessura = value;
                RaisePropertyChanged("Espessura");
                Update();
            }
        }
        int _espessura = 10;


        public BitmapSource ImageSource { get { return _imageSource; } }
        BitmapSource _imageSource;


        private async Task Update()
        {
            await Dispatcher.BeginInvoke(new Action(() =>
            {
                // somewhat lengthy operation
                _imageSource = FranjasSenoidais.Criar((int)franjas.Width,
                                       (int)franjas.Height,
                                       Maximo, Minimo,
                                       Espessura);

            }));

            RaisePropertyChanged("ImageSource");
        }

        public MainWindow()
        {
            InitializeComponent();

            DataContext = this;
        }




        public event PropertyChangedEventHandler PropertyChanged;
        void RaisePropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }
}

FranjasSenoidais.cs

public static class FranjasSenoidais
{
    public static BitmapSource Criar(int largura,
                                     int altura,
                                     double maximo,
                                     double minimo,
                                     int espessura)
    {
        if (largura < 2 || altura < 2)
            return null;

        var targetBitmap = new RenderTargetBitmap(largura, altura,
                                                96, 96,
                                                PixelFormats.Pbgra32);

        var visual = new DrawingVisual();

        using (var dc = visual.RenderOpen())
        {
            double xcenter = largura * 0.5;
            double ycenter = altura * 0.5;

            dc.DrawEllipse(Brushes.Black, null, new Point(xcenter, ycenter), espessura, espessura);
        }

        targetBitmap.Render(visual);
        targetBitmap.Freeze();

        return targetBitmap;
    }
}

【问题讨论】:

  • 对我来说 MainWindow :窗口,INotifyPropertyChanged 已经错了。 Window 不应该是 INotifyPropertyChanged,只有 viewmodel 应该
  • @Steve 我知道,这就是我写“为简洁起见”观察的原因。此外,这个问题与 MVVM 无关,如果你没有绑定到 MVVM,任何东西都可以实现 INPC、IMO。
  • 使用 Dispatcher.BeginInvoke 没有帮助,因为当前调度程序是 UI 线程调度程序。尝试用 Task.Run 替换 Dispatcher.BeginInvoke。这将安排线程池上的图像加载。另外,我认为您可以使用 void 作为 Update 方法的返回类型,因为您不需要在设置器中“等待”它。
  • @CoolBlue 当我按照你的建议做时,我得到了一个InvalidOperationException,因为该对象不能被不同的线程访问(这是由于Task.Run() 我猜想)。
  • 我想如果你按照我的建议做但是添加 MainWindow.Dispatcher.Invoke(()=>RaisePropertyChanged("ImageSource")) 就可以了。

标签: c# wpf asynchronous inotifypropertychanged


【解决方案1】:

我认为您只需要进行一些更改。 @Cool-Blue 评论是对的。只需将调度移动到您的 INotify 实现中,并将您的 cpu 绑定进程排队到一个任务中。请务必触发从该线程更改的属性,请参见下文:

    private void Update() {
        Task.Run(new Action(() => {
            // somewhat lengthy operation
            _imageSource = FranjasSenoidais.Criar((int)franjas.Width,
                                   (int)franjas.Height,
                                   Maximo, Minimo,
                                   Espessura);
            RaisePropertyChanged("ImageSource");
        }));
    }

    public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(string name) {
        Application.Current.Dispatcher.Invoke(() => {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }, System.Windows.Threading.DispatcherPriority.Background);
    }

【讨论】:

  • 仍然得到InvalidOperation,因为franjas 在另一个线程中,并且它的属性在Task.Run() 调用中被访问。
  • 其实设置的是_imageSource
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-08
  • 2013-05-09
  • 2017-02-12
  • 2020-01-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多