Выполнить команду после того, как представление загружено WPF MVVM
У меня есть проект на основе WPF и MVVM. Мой проект основан на мастере, содержащем элемент управления содержимым, который отображает мои представления (пользовательские элементы управления). Я хочу выполнить команду после полной загрузки представления, я бы хотел, чтобы пользователь увидел пользовательский интерфейс представления сразу после ее выполнения.
Я пытался с помощью:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding StartProgressCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Но команда выполняется до того, как я вижу интерфейс просмотра, и это не то, что я ищу.
У кого-нибудь есть идеи, как мне нужно это реализовать?
8 ответов
Это потому, что, хотя технически представление загружено (то есть: все компоненты готовы в памяти), ваше приложение еще не находится в режиме ожидания, и поэтому пользовательский интерфейс еще не обновлен.
Установка команды с использованием триггеров взаимодействия на Loaded
событие уже хорошо, так как нет лучшего события для прикрепления.
Теперь, чтобы действительно дождаться отображения интерфейса, сделайте это в своем StartProgress()
(Я предполагаю, что это имя метода, который StartProgressCommand
указать на):
public void StartProgress()
{
new DispatcherTimer(//It will not wait after the application is idle.
TimeSpan.Zero,
//It will wait until the application is idle
DispatcherPriority.ApplicationIdle,
//It will call this when the app is idle
dispatcherTimer_Tick,
//On the UI thread
Application.Current.Dispatcher);
}
private static void dispatcherTimer_Tick(object sender, EventArgs e)
{
//Now the UI is really shown, do your computations
}
Вы можете использовать Dispatcher для этого и установить приоритет ApplicationIdle, чтобы он выполнялся, когда все закончится.
Application.Current.Dispatcher.Invoke(
DispatcherPriority.ApplicationIdle,
new Action(() =>
{
StartProgressCommand.Invoke(args);
}));
дополнительная информация об диспетчере http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority.aspx
веселит. Ste.
Другой способ сделать это:
определить это xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
а также xmlns:mi="http://schemas.microsoft.com/expression/2010/interactions"
на вашем пользовательском контроле XAML и добавьте сборку Microsoft.Expression.Interactions в ваш проект. используйте CallMethodAction на вашем триггере, как показано ниже:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<mi:CallMethodAction TargetObject="{Binding}" MethodName="StartProgressCommand"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Поместите триггер в корневой элемент вашего пользовательского элемента управления, например: grid. И измените ваш StartProgressCommand в вашем классе ViewModel с команды на обычный старый обычный метод, например:
public void StartProgressCommand()
{
/* put your program logic here*/
}
Он будет запускать метод ровно один раз каждый раз, когда будет отображаться ваш пользовательский элемент управления.
Мы используем решение таймера - я тоже был очень сомнителен в этом, но он, кажется, работает нормально.
public static class DispatcherExtensions
{
private static Dictionary<string, DispatcherTimer> timers =
new Dictionary<string, DispatcherTimer>();
private static readonly object syncRoot = new object();
public static void DelayInvoke(this Dispatcher dispatcher, string namedInvocation,
Action action, TimeSpan delay,
DispatcherPriority priority = DispatcherPriority.Normal)
{
lock (syncRoot)
{
RemoveTimer(namedInvocation);
var timer = new DispatcherTimer(delay, priority, (s, e) =>
{
RemoveTimer(namedInvocation);
action();
}, dispatcher);
timer.Start();
timers.Add(namedInvocation, timer);
}
}
public static void CancelNamedInvocation(this Dispatcher dispatcher, string namedInvocation)
{
lock (syncRoot)
{
RemoveTimer(namedInvocation);
}
}
private static void RemoveTimer(string namedInvocation)
{
if (!timers.ContainsKey(namedInvocation)) return;
timers[namedInvocation].Stop();
timers.Remove(namedInvocation);
}
}
Затем мы вызываем с помощью
Dispatcher.CurrentDispatcher.DelayInvoke("InitSomething",()=> {
DoSomething();
},TimeSpan.FromSeconds(1));
Я пришел с этим решением для этого. Я хотел использовать логическое свойство, установленное как true в начале работы и false в конце, чтобы позволить пользователю уведомлять о фоновой работе.
В основном, он использует
DispatcherTimer
запустить метод после рендеринга интерфейса в соответствии с этим- Асинхронный метод, который выполнит
Action
передается в качестве параметра
Вызов:
this.LaunchThisWhenUiLoaded(() => { /*Stuff to do after Ui loaded here*/ });
Метод:
private DispatcherTimer dispatchTimer;
private Action ActionToExecuteWhenUiLoaded;
/// <summary>
/// Handy method to launch an Action after full UI rendering
/// </summary>
/// <param name="toExec"></param>
protected void LaunchThisWhenUiLoaded(Action toExec)
{
ActionToExecuteWhenUiLoaded = toExec;
// Call UiLoaded method when UI is loaded and rendered
dispatchTimer = new DispatcherTimer(TimeSpan.Zero, DispatcherPriority.ContextIdle, UiLoaded, Application.Current.Dispatcher);
}
/// <summary>
/// Method called after UI rendered
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected async void UiLoaded(object sender, EventArgs e)
{
this.IsBusy = true;
if (ActionToExecuteWhenUiLoaded != null)
await Task.Run(ActionToExecuteWhenUiLoaded);
dispatchTimer.Stop();
this.IsBusy = false;
}
Может быть, не чистый, но он работает, как ожидалось.
Вы пробовали связываться с событием ContentRendered? Это произойдет после загруженного события, но я не уверен, является ли это гарантией того, что поток пользовательского интерфейса закончил рисовать окно.
Вы можете написать "Thread.Sleep(10000)" в первой строке метода "CommandExecute". Используйте тот же загруженный триггер.
если вы не хотите использовать Thread.Sleep, вы можете перейти к "DispatcherTimer". Запустите таймер в вашем методе execute команды и переключите весь ваш код на событие таймера. установите интервал таймера на 2 секунды, чтобы пользователь отправлял пользовательский интерфейс.
Вы можете проверить свойство IsLoaded для представления, когда представление находится в загруженной форме, оно возвращает false, когда представление полностью загружено, это свойство становится истинным.
Спасибо, Раджникант