【问题标题】:Execute command after view is loaded WPF MVVM加载视图后执行命令 WPF MVVM
【发布时间】:2012-10-13 04:07:00
【问题描述】:

我有一个基于 WPF 和 MVVM 的项目。 我的项目基于一个向导,其中包含一个显示我的视图的内容控件(用户控件) 我想在视图完全加载后执行命令,我希望用户在命令执行后立即看到视图 UI。

我尝试使用:

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

但是在我看到视图 UI 之前执行了命令,这不是我想要的。

有人知道我应该如何实现它吗?

【问题讨论】:

    标签: wpf mvvm triggers command


    【解决方案1】:

    另一种方法:

    在您的用户控件 XAML 上定义此 xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"xmlns:mi="http://schemas.microsoft.com/expression/2010/interactions" 并在您的项目中添加 Microsoft.Expression.Interactions 程序集。在触发器上使用 CallMethodAction,如下所示:

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

    将触发器放在用户控件的根元素中,例如:grid.并将您的 ViewModel 类中的 StartProgressCommand 从命令更改为普通的旧常规方法,例如:

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

    每次您的用户控件呈现时,它都会运行该方法一次。

    【讨论】:

    • 简单而精确...刚刚用缺少的xmlns:i编辑
    • 这对我不起作用它告诉我该方法或操作没有实现
    • 每次渲染视图时都会调用loaded事件。当有视图添加到此父视图时,这可能会导致问题。
    【解决方案2】:

    我为这个解决方案提供了这个解决方案。 我想在工作开始时使用设置为 true 并在结束时设置为 false 的布尔属性,以便通知用户后台工作。

    基本上,它使用

    • 一个DispatcherTimer根据this在UI渲染后启动一个方法@
    • 一个异步方法将执行作为参数传递的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;
    }
    

    也许不是很干净,但它可以按预期工作。

    【讨论】:

      【解决方案3】:

      您可以为此使用 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的信息

      干杯。 步骤。

      【讨论】:

      • 这似乎是比公认的答案更清洁的解决方案。当你只打算运行一个周期时,为什么要启动一个计时器?
      • Invoke 似乎对我不起作用,所以我使用了BeginInvoke 而不是Invoke。效果很好。
      【解决方案4】:

      这是因为即使从技术上讲视图已加载(即所有组件都已在内存中准备就绪),您的应用还没有空闲,因此 UI 还没有刷新。

      Loaded 事件上使用交互触发器设置命令已经很好,因为没有更好的事件可以附加到。
      现在要真正等到 UI 显示出来,在您的 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
      }
      

      【讨论】:

      • 老我知道,但对于那些匆忙的人来说,这里是如何结束线程:在你的事件处理程序中 var timer = sender as DispatcherTimer; timer.Stop();
      • 我真的不知道,如果这是一个很好的方法,但它确实有效!
      【解决方案5】:

      我们使用计时器解决方案 - 我也对此非常怀疑,但它似乎工作正常。

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

      【讨论】:

        【解决方案6】:

        你可以在“CommandExecute”方法的第一行写一个“Thread.Sleep(10000)”。使用相同的加载触发器。

        如果您不想使用 Thread.Sleep,那么您可以选择“DispatcherTimer”。在您的命令执行方法中启动一个计时器并将所有代码转移到计时器滴答事件。 将您的计时器间隔设置为 2 秒,以便用户发送 UI。

        【讨论】:

        • 我想过,但如果可能的话,我宁愿避免基于时间假设执行命令
        • 即使我不喜欢在我的应用程序中使用计时器,但我认为 WPF 中没有任何事件会在加载后几秒钟后触发。如果您想在执行命令之前显示一段时间的 UI,那么您将不得不使用计时器。
        • 同意奥菲尔。作为一般规则,永远不要假设时间来解决竞争条件。正如 Louis Kauffman 所示,在 Application.Idle 上触发是最好的。
        【解决方案7】:

        您是否尝试绑定到ContentRendered 事件?它会在加载事件之后发生,但我不确定这是否是 UI 线程已经完成绘制窗口的保证。

        【讨论】:

        • 我使用用户控件而不是窗口,因此事件 ContentRendered 不存在。
        • 你的控件是动态添加到窗口的吗?如果没有,绑定到父窗口的事件
        【解决方案8】:

        您可以查看视图的 IsLoaded 属性,当视图处于加载状态时返回 false,当视图完全加载时,该属性变为 true。

        谢谢, 拉杰尼坎特

        【讨论】:

        • 它不工作。 isLoaded 在视图渲染之前获取真值
        猜你喜欢
        • 2013-05-17
        • 2018-03-17
        • 2022-06-10
        • 1970-01-01
        • 2010-11-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多