Окно приложения блокируется, пока активна операция рендеринга. т.е. когда установлено свойство Content элемента управления ContentControl. Рисуется пользовательский элемент управления, который является DataTemplate для содержимого. Замораживание длится от 5 до 10 секунд в зависимости от используемого ПК.
Этот пользовательский элемент управления не слишком сложен (около 250 простых элементов управления — изображения, текстовые поля, текстовые блоки, кнопки и т. д.). Вёрстка далека от совершенства, её писал не я, да и времени на оптимизацию вёрстки нет, да и не хочется, так как проблему можно было бы в лучшем случае уменьшить.
Лучшее, что мне удалось сделать, это обернуть элемент управления в «контейнер», который умеет рисовать анимацию загрузки и показывать занятый курсор перед зависанием окна пользовательского интерфейса/приложения. Ниже я привожу полный листинг кода.
Я прокомментировал «замораживание начинается здесь» в коде внизу вопроса в коде пользовательского элемента управления оболочки. Именно тогда механизм рендеринга WPF начинает рисовать пользовательский элемент управления (то есть сетку внутри него).
Я много пользовался своей любимой поисковой системой и узнал, что в WPF есть специальный поток рендеринга, который отделен от потока пользовательского интерфейса.
Другое, что скрывает окно приложения, пока оно заморожено, и отображает окно анимации «загрузки» в это время (или что-то производное от этого), что легко, учитывая приведенный ниже код, но абсурдно - есть ли способ смягчить ситуацию?
Вот код, сначала вариант использования:
<!-- while I am being rendered, I block the UI thread. -->
<UserControl x:Class="MyUserControl"
xmlns:loading="clr-namespace:Common.UI.Controls.Loading;assembly=Common.UI.Controls">
<loading:VisualElementContainer>
<loading:VisualElementContainer.VisualElement>
<Grid>
<!-- some 500 lines of using other controls with binding, templates, resources, etc..
for the same effect try having a canvas with maaany rectangles..-->
</Grid>
</loading:VisualElementContainer.VisualElement>
</loading:VisualElementContainer>
</UserControl>
Макет пользовательского элемента управления оболочки:
<Style TargetType="{x:Type loading:VisualElementContainer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type loading:VisualElementContainer}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<loading:LoadingAnimation x:Name="LoadingAnimation" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<ContentControl x:Name="ContentHost"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
И пользовательский код управления оболочкой:
/// <summary>Hosts the visual element and displays a 'loading' animation and busy cursor while it is being rendered.</summary>
public class VisualElementContainer : Control
{
static VisualElementContainer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(VisualElementContainer), new FrameworkPropertyMetadata(typeof(VisualElementContainer)));
}
private Window MyWindow;
private ContentControl ContentHost;
private LoadingAnimation LoadingAnimation;
public override void OnApplyTemplate()
{
this.ContentHost = this.GetTemplateChild("ContentHost") as ContentControl;
this.LoadingAnimation = this.GetTemplateChild("LoadingAnimation") as LoadingAnimation;
base.OnApplyTemplate();
this.MyWindow = this.FindVisualParent(typeof(Window)) as Window;
this.SetVisual(this.VisualElement);
}
private static DependencyProperty VisualElementProperty =
DependencyProperty.Register(
"VisualElement",
typeof(FrameworkElement),
typeof(VisualElementContainer),
new PropertyMetadata(null, new PropertyChangedCallback(VisualElementPropertyChanged)));
public FrameworkElement VisualElement
{
get { return GetValue(VisualElementProperty) as FrameworkElement; }
set { SetValue(VisualElementProperty, value); }
}
private static void VisualElementPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var me = sender as VisualElementContainer;
if (me == null || me.ContentHost == null || me.LoadingAnimation == null)
return;
me.RemoveVisual(e.OldValue as FrameworkElement);
me.SetVisual(e.NewValue as FrameworkElement);
}
private void RemoveVisual(FrameworkElement fwElement)
{
this.ContentHost.Content = null;
if (fwElement != null)
fwElement.Loaded -= fwElement_Loaded;
}
private void SetVisual(FrameworkElement fwElement)
{
if (fwElement == null)
{
this.ContentHost.Content = fwElement;
}
else
{
fwElement.Loaded += fwElement_Loaded;
this.SetContentVisibility(false);
this.Dispatcher
.BeginInvoke(
//freeze begins here
new Action(() => this.ContentHost.Content = fwElement),
System.Windows.Threading.DispatcherPriority.ContextIdle);
}
}
private void fwElement_Loaded(object sender, RoutedEventArgs e)
{
this.SetContentVisibility(true);
//freeze ends here.
}
private void SetContentVisibility(bool isContentVisible)
{
if (isContentVisible)
{
this.MyWindow.Cursor = Cursors.Arrow;
this.LoadingAnimation.Visibility = Visibility.Collapsed;
this.ContentHost.Visibility = Visibility.Visible;
}
else
{
this.MyWindow.Cursor = Cursors.Wait;
this.ContentHost.Visibility = Visibility.Hidden; //Setting to collapsed results in the loaded event never fireing.
this.LoadingAnimation.Visibility = Visibility.Visible;
}
}
}
WrapPanel
с 1000+ кнопок в качестве VisualElement, и он появляется мгновенно... Есть ли что-то вроде большого количества данных, которые нужно где-то загрузить в контекстах данных? - person Roel van Westerop   schedule 28.08.2014