【发布时间】: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