C# 在UI线程上执行长任务
我正在开发一个简单的自定义控件,它应该充当菜单系统。它由两个按钮组成:返回和主页以及上面的菜单项。 ControlTemplate如下所示:C# 在UI线程上执行长任务,c#,wpf,xaml,user-interface,binding,C#,Wpf,Xaml,User Interface,Binding,我正在开发一个简单的自定义控件,它应该充当菜单系统。它由两个按钮组成:返回和主页以及上面的菜单项。 ControlTemplate如下所示: <ControlTemplate x:Key="FancyColorPickerTemplate"> <menu:BusyDecorator x:Name="BusyDecorator" Style="{StaticResource BusyDecoratorStyle}"> <m
<ControlTemplate x:Key="FancyColorPickerTemplate">
<menu:BusyDecorator x:Name="BusyDecorator" Style="{StaticResource BusyDecoratorStyle}">
<menu:BusyDecorator.IsBusyIndicatorShowing>
<PriorityBinding>
<Binding Path="IsBusy" RelativeSource="{RelativeSource AncestorType={x:Type CustomControls:Menu}}"/>
</PriorityBinding>
</menu:BusyDecorator.IsBusyIndicatorShowing>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.70*"/>
<RowDefinition Height="0.30*"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" Name="Part_ItemControl"
ItemTemplateSelector="{StaticResource imgStringTemplateSelector}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Button Grid.Row="1" Name="PART_BackButton" FontSize="20" Content="Back" HorizontalAlignment="Left" />
<Button Grid.Row="1" Name="PART_HomeButton" FontSize="20" Content="Home" HorizontalAlignment="Center" />
</Grid>
</menu:BusyDecorator>
</ControlTemplate>
在ItemsControl上有一个ItemTemplateSelector,用于选择显示元素的DataTemplate(可以是按钮,也可以是UserControl)。
例如:
<DataTemplate x:Key="ButtonTemplate">
<Grid Margin="10,0,10,0">
<Button Content="{Binding Title}" ></Button>
</Grid>
</DataTemplate>
<DataTemplate x:Key="UserControlTemplate">
<Grid Margin="10,0,10,0">
<CustomControls:ColorPickerUserControl Width="200" Height="200"/>
</Grid>
</DataTemplate>
在Codebehind中,我检查单击了哪个元素,并根据需要加载子菜单(通过设置ItemsControl的ItemsSource属性):
void Menu_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
ItemsControl itemsControl = GetTemplateChild("Part_ItemControl") as ItemsControl;
object item = GetElementFromPoint(itemsControl, e.GetPosition(itemsControl));
if (item != null && item is MenuItem2)
{
MenuItem2 mi = item as MenuItem2;
if (mi.SubMenu != null)
{
IsBusy = true;
Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
MenuItems = new ObservableCollection<MenuItem2>(mi.SubMenu.Items);
IsBusy = false;
}));
m_MenuStack.Push(mi.SubMenu);
}
else
{
if (!IsBusy)
{
ExecuteAction(mi.Ac);
}
}
}
else
Console.WriteLine("no item found");
}
void菜单\u预览MouseLeftButtonUp(对象发送器,鼠标按钮ventargs e)
{
ItemsControl ItemsControl=GetTemplateChild(“Part_ItemControl”)作为ItemsControl;
对象项=GetElementFromPoint(itemsControl,e.GetPosition(itemsControl));
如果(item!=null&&item为MenuItem2)
{
MenuItem2 mi=作为MenuItem2的项目;
如果(mi.子菜单!=null)
{
IsBusy=true;
Dispatcher.BeginInvoke(DispatcherPriority.Background,新操作(()=>
{
MenuItems=新的ObservableCollection(mi.子菜单项);
IsBusy=false;
}));
菜单回推(mi.子菜单);
}
其他的
{
如果(!正忙)
{
执行(mi.Ac);
}
}
}
其他的
Console.WriteLine(“未找到项”);
}
上面代码中的菜单项绑定到ItemsSource属性,该属性将重新评估ItemsControl并基于DataTemplateSelector应用相应的DataTemplate。
我的代码中的问题在于上面的IsBusy属性,它应该显示BusyDecorator(请参见xaml),而DataTemplate是一个需要很长时间才能显示的UserControl。它不起作用,因为我猜UserControl正在UI线程上加载,并且IsBusy属性会触发UI线程上的操作
我是不是走错了路?有什么方法可以使此工作正常吗?您可以始终设置忙标志,并在忙集的渲染完成后在UI线程上安排其余工作 这里的神奇之处在于,线程是使用优先级
System.Windows.Threading.DispatcherPriority.Render调度的,它使任务与渲染同时运行
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Click="Button_Click_1">LongTask</Button>
<TextBlock Grid.Row="1" Text="{Binding IsBusy}"/>
</Grid>
</Window>
我建议使用.NET4.5,然后使用Async/Await特性实现它,这将是一个很好的解决方案。不幸的是,我们的嵌入式系统上只有framework 3.5。您可以走很长的路(实际上是Async/Await所做的),将其放在一个单独的线程中,并实现一个处理程序,当加载线程完成时,它会收到一个通知。我已经尝试使用单独的线程,但它不起作用。我想,问题是UserControl是由UI线程构造的,我不知道如何避免这种情况。
using System;
using System.Threading;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public bool IsBusy
{
get { return (bool)GetValue(IsBusyProperty); }
set { SetValue(IsBusyProperty, value); }
}
public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register("IsBusy", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
IsBusy = true;
this.Dispatcher.BeginInvoke(new Action(() =>
{
Thread.Sleep(2000);
IsBusy = false;
}), System.Windows.Threading.DispatcherPriority.Render, null);
}
}
}