Execute command after view is loaded WPF MVVM

跟風遠走 提交于 2019-12-03 05:02:39

问题


I have a project based WPF and MVVM. My project is based on a wizard containing a content control which shows my views (User Controls) I want to execute a command after the view is loaded completely, I would like the user to see the view UI immediately after the command will be executed.

I tried using :

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

But the command is executed before I see the view UI and it's not what I'm looking for.

Does anyone have an idea how should I need to implement it?


回答1:


That's because even though technically the view is loaded (i.e: all the components are ready in memory), your app is not idle yet, and thus the UI isn't refreshed yet.

Setting a command using interaction triggers on the Loaded event is already good, as there is no better event to attach to.
Now to really wait until the UI is shown, do this in your StartProgress() (I'm assuming here that this is the name of the method that StartProgressCommand point to):

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:


You could use the Dispatcher for this and set the priority to ApplicationIdle so that it will on execute when everything has finished

            Application.Current.Dispatcher.Invoke(
            DispatcherPriority.ApplicationIdle,
            new Action(() =>
            {
               StartProgressCommand.Invoke(args);

            }));

more information on the dispatcher http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority.aspx

cheers. ste.




回答3:


We use a the timer solution - i too was very dubious about this but it does seem to work fine.

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


} 

Then we invoke using

Dispatcher.CurrentDispatcher.DelayInvoke("InitSomething",()=> {
    DoSomething();
},TimeSpan.FromSeconds(1));



回答4:


another way to do it:

define this xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" and xmlns:mi="http://schemas.microsoft.com/expression/2010/interactions" on your usercontrol XAML and add Microsoft.Expression.Interactions assembly on your project. use CallMethodAction on your trigger, just as bellow:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <mi:CallMethodAction TargetObject="{Binding}" MethodName="StartProgressCommand"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

Put the triger inside the root element of your usercontrol, e.g: grid. And change your StartProgressCommand, in your ViewModel class, from command to plain old regular Method, e.g:

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

It'll run the method exactly one time every time your user control rendered.




回答5:


You can check IsLoaded property for view, when view is in loaded form it returns false, when view is fully loaded, this property become true.

Thanks, Rajnikant




回答6:


Have you tried binding to the ContentRendered event? It will occur after the loaded event, yet I´m not sure whether or not this is a gurantee that the UI thread has finished painting the window then.




回答7:


You can Write a "Thread.Sleep(10000)" in the first line of "CommandExecute" method. Use the same loaded trigger.

if you don't want to use Thread.Sleep then you can go for "DispatcherTimer". Start a timer in your command execute method and shift all your code to timer tick event. set your timer interval to 2 seconds, so that user will sen the UI.




回答8:


I came with this solution for this one. I wanted to use a boolean property set as true at start of work and to false at the end to allow to notify user of background work.

Basically, it uses

  • a DispatcherTimer to launch a method after UI render according to this
  • An async method wich will execute the Action passed as a parameter

Call :

this.LaunchThisWhenUiLoaded(() => { /*Stuff to do after Ui loaded here*/ });

Method :

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

Maybe not the clean but it works as expected.



来源:https://stackoverflow.com/questions/13026826/execute-command-after-view-is-loaded-wpf-mvvm

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!