Выполнение команды после просмотра загружается WPF MVVM

У меня есть проект WPF и MVVM. Мой проект основан на мастере, содержащем элемент управления содержимым, который показывает мои представления (User Controls) Я хочу выполнить команду после полной загрузки представления, я хотел бы, чтобы пользователь увидел интерфейс представления сразу после выполнения команды.

Я попытался использовать:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding StartProgressCommand}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

Но команда выполняется до того, как я вижу пользовательский интерфейс представления, и это не то, что я ищу.

Есть ли у кого-нибудь идея, как мне его реализовать?

Ответы

Ответ 1

Это потому, что хотя технически представление загружено (т.е. все компоненты готовы в памяти), ваше приложение еще не праздно, и, следовательно, пользовательский интерфейс еще не обновлен.

Настройка команды с помощью триггеров взаимодействия в событии 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
}

Ответ 2

Вы можете использовать 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.

Ответ 3

Мы используем решение таймера - я тоже очень сомневался в этом, но он работает нормально.

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));

Ответ 4

другой способ сделать это:

определите это 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>

Поместите триггер внутри корневого элемента вашего usercontrol, например: grid. И измените свой StartProgressCommand в классе ViewModel, от команды до обычного старого обычного метода, например:

public void StartProgressCommand()
{
  /* put your program logic here*/
}

Он будет запускать метод ровно один раз каждый раз, когда ваш пользовательский элемент управления будет отображаться.

Ответ 5

Вы можете проверить свойство IsLoaded для просмотра, когда представление находится в загруженной форме, оно возвращает false, когда представление полностью загружено, это свойство становится истинным.

Спасибо, Rajnikant

Ответ 6

Вы пытались привязаться к событию ContentRendered? Это произойдет после загруженного события, но я не уверен, является ли это гарантом того, что поток пользовательского интерфейса закончил рисовать окно.

Ответ 7

Вы можете написать "Thread.Sleep(10000)" в первой строке метода "CommandExecute". Используйте тот же загруженный триггер.

если вы не хотите использовать Thread.Sleep, тогда вы можете перейти на "DispatcherTimer". Запустите таймер в методе выполнения команды и смените весь свой код на событие таймера. установите интервал таймера на 2 секунды, чтобы пользователь сменил пользовательский интерфейс.

Ответ 8

Я пришел с этим решением для этого. Я хотел использовать логическое свойство, установленное как true при начале работы, и false в конце, чтобы дать возможность уведомлять пользователя о фоновой работе.

В основном, он использует

  • a 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;
}

Может быть, не чистый, но он работает так, как ожидалось.