【问题标题】:Unusual disabling behavior with Command buttons使用命令按钮的异常禁用行为
【发布时间】:2016-03-29 21:46:14
【问题描述】:

我很抱歉这么罗嗦,但我想让情况完全清楚。如果您是 WPF 专业人士,请看一下。

有两个CollectionViewSource 绑定到ItemsControls,它们使用UserControl 视图来显示StackPanels 或自定义Buttons。下面的屏幕截图中显示的每一侧都有一个。我遇到的问题是,当设置父集合属性时,DataTemplate 视图中的所有按钮都被禁用。即使是上面的 2 个按钮也有同样的问题,即使它们在我最近的编辑之前工作。

如果我单击表单或按任意键,按钮将启用。一旦属性重置为新编辑和排序的集合,它们就会再次禁用。冲洗并重复。这就是它的样子。第一帧是它的启动方式(灰色使用StackPanel),第二帧是单击 RFS 按钮时的样子,第三帧是我单击任意位置或按键时发生的情况。

我一直在兜圈子尝试一些事情。唯一似乎有效的是代码隐藏的解决方法,它将焦点集中在一件事上,然后再返回。但是,如果用户尝试使用其他仪表板项目之一,这对用户不利。

由于所有这些的 WPF 非常庞大,我将尝试仅包含相关部分。这些是 TabItemControl (UserControl) 上的 ItemsControls。

        <!-- BID (SELL) DEPTH -->
        <ItemsControl Grid.Row="0" Grid.Column="0" x:Name="bidDepthList" ItemsSource="{Binding Source={StaticResource BidDepthCollection}}"
                      Visibility="{Binding Path=IsMultilegEnabled, Converter={StaticResource CollapsedConverter}}">
           <ItemsControl.ItemTemplate>
              <DataTemplate DataType="{x:Type viewmodels:DepthLevelViewModel}">
                 <v:DepthLevelRowView x:Name="BidDepthLevelRowViewControl" DataContext="{Binding}" HorizontalAlignment="Center" Margin="1,0,1,3" />
              </DataTemplate>
           </ItemsControl.ItemTemplate>
        </ItemsControl>

        <!-- ASK (BUY) DEPTH -->
        <ItemsControl Grid.Row="0" Grid.Column="1" x:Name="askDepthList" ItemsSource="{Binding Source={StaticResource AskDepthCollection}}"
                      Visibility="{Binding Path=IsMultilegEnabled, Converter={StaticResource CollapsedConverter}}">
           <ItemsControl.ItemTemplate>
              <DataTemplate DataType="{x:Type viewmodels:DepthLevelViewModel}">
                 <v:DepthLevelRowView x:Name="AskDepthLevelRowViewControl" DataContext="{Binding}" HorizontalAlignment="Center" Margin="1,0,1,3" />
              </DataTemplate>
           </ItemsControl.ItemTemplate>
        </ItemsControl>

正在使用的视图在网格内有 4 个“控件”。根据状态(RFS OFF/RFS ON)和它们在哪一侧(卖出/买入),一次只显示一个。其他人都崩溃了。如您所见,这很好。

它们之间唯一的共同点是它们设置了Command,顶部的大多数控件都正确禁用/启用。如果采取任何鼠标或键盘操作,按钮正确启用的事实告诉我CanExecute 处理程序正在工作,但不是立即工作。其他控件在我创建 these changes 后开始工作,但随后大按钮开始像深度按钮一样出现异常。

我在更改集合后尝试使用 CommandManager.InvalidateRequerySuggested();,但这没有帮助。

注意:即使是这样简单的事情也会发生这种情况:

<Button x:Name="TestBuyButton" Command="{x:Static ptcommands:OrderCommands.Buy}" CommandTarget="{Binding RelativeSource={RelativeSource Self}}" Content="Test Buy" />

我知道布尔值设置正确,因为我添加了测试 CheckBoxes 以使用 IsChecked 显示当前值。而且,一旦采取任何输入操作,它们都会启用,包括极其基本的 Button。

我还有什么遗漏或可以采取的不同方法吗?


编辑:我最终使用Dispatcher 从事件线程调用我的显示更新例程到 UI 线程。布尔值已设置,但 WPF 仍然没有重新查询 Command。但是.. CommandManager.InvalidateRequerySuggested() 电话然后起作用了!我唯一不喜欢的是它听起来像是广播失效。我不希望重新查询所有命令。我只想重新查询 Buy 和 Sell 命令。我已经尝试了各种奇怪的方法来让那些工作正常,但到目前为止除了全局之外没有任何工作。

编辑 2:似乎InvalidateRequerySuggested 是要走的路。

来自 MSDN CommandManager.InvalidateRequerySuggested:

CommandManager 在确定命令目标何时更改时仅关注某些条件,例如键盘焦点的更改。在 CommandManager 无法充分确定导致命令无法执行的条件变化的情况下,可以调用 InvalidateRequerySuggested 来强制 CommandManager 引发 RequerySuggested 事件。

这可以解释为什么焦点改变和按键会导致按钮启用。该页面还显示将呼叫置于计时器中。因此,我认为它不像我想象的那样占用资源。所以,我想这是我的永久解决方案。

【问题讨论】:

  • 我做了another post 关于我在尝试解决此问题时所做的一些更改。由于它现在只填充一次并且只更新它所拥有的内容,因此它只被禁用一次。我可以单击表单,它不会再次禁用。我不知道当我再次添加/删除时是否会这样做,因为当前进程的填充速度非常快,然后会更改现有项目。因此,从技术上讲,禁用问题仍然存在。
  • 我刚刚对这篇文章做了很大的修改以反映当前的情况。

标签: c# wpf xaml mvvm


【解决方案1】:

我必须进行一些更改才能在不更改焦点的情况下启用按钮。

布尔值是在从事件线程调用的方法中设置的。我曾尝试同时拨打CommandManager.InvalidateRequerySuggested,但什么也没发生。最后我认为这可能与线程有关。这就是最终解决它的原因。

在选项卡管理器类(包含事件处理程序和其他逻辑)可以访问的父窗体中,我添加了一个调用方法:

  Public Sub InvokeOnUiThread(ByRef uiAction As Action, Optional ByRef doAsync As Boolean = False)

     Dim dispatchObject As Dispatcher = orderTicketView.Dispatcher

     If (dispatchObject Is Nothing OrElse dispatchObject.CheckAccess()) Then
        uiAction()
     Else
        If doAsync Then
           dispatchObject.BeginInvoke(uiAction, DispatcherPriority.Normal)
        Else
           dispatchObject.Invoke(uiAction, DispatcherPriority.Normal)
        End If
     End If

  End Sub

在选项卡管理器事件处理程序中,我将其更改为通过调用程序调用更新例程:

formOrderTicketView.InvokeOnUiThread(New Action(AddressOf UpdateButtons))

在UpdateButtons 方法的底部,如果进行了需要重新查询的更改,我添加了对CommandManager 的调用:

CommandManager.InvalidateRequerySuggested()

我不知道这会对性能造成什么类型​​的影响,但显然 WPF 在焦点更改等时会以自己的方式执行它。直接调用它是建议的强制方法。

来自 MSDN CommandManager.InvalidateRequerySuggested:

CommandManager 在确定命令目标何时更改时仅关注某些条件,例如键盘焦点的更改。在 CommandManager 无法充分确定导致命令无法执行的条件变化的情况下,可以调用 InvalidateRequerySuggested 来强制 CommandManager 引发 RequerySuggested 事件。

由于它现在正在工作,我将其作为解决方案。

【讨论】:

    【解决方案2】:

    绑定到按钮的命令...有 CanExecute 委托吗?如果是这样,您必须在应该重新评估该委托时引发 CanExecuteChanged。另外,请确保 CanExecute 的实现没有被破坏并错误地返回 false;

    【讨论】:

    • 我没有添加更多场景注释,因为它会更加冗长,但是......作为我在将其移动到视图之前的初始设计测试的一部分,我直接放置了按钮在数据模板中。它没有像现在那样获得禁用效果,并且命令运行良好,但后台转换器停止工作。现在转换器工作,备用显示器工作。在单击或按键之前,这只是奇怪的禁用。
    • 另外,命令都定义为GlobalRoutedUICommands,它本身就是OrderCommands类中继承RoutedUICommand并实现INotifyPropertyChanged的类。 EventHandlers 在所有其他视图模型继承并允许其他类(例如选项卡管理器)订阅的 2 级视图模型中定义。我环顾四周,没有看到任何 CanExecute 代表。列表上方的大按钮也使用来自同一个 OrderCommands 类的相同命令结构。
    • 我还想补充一点,这是一个交易平台,随时都有线程在各处触发。各种更新随处发生。任何会导致性能问题的事情都需要避免。所有命令都可以随时执行。代码隐藏在需要时进行检查。所以,如果 CanExecute 总是返回 True 会有帮助,我可以试着找出把它放在哪里。这东西不是我设计的。
    • 我编辑了我的初始帖子以包括如何声明命令。我已经尝试寻找使用 CanExecute 的方法,但我发现的所有内容都假定命令是在同一表单中声明的​​,并且它们都不是RoutedUICommand。我希望看到命令声明会有所帮助。
    • 我评论了上面CanExecuteCustomCommand 中的所有内容,只有e.CanExecute = true,但没有变化。在我单击表单之前,这些按钮仍处于禁用状态。我的老板要生气了。呃。
    猜你喜欢
    • 2012-10-07
    • 2011-07-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多