Silverlight - 将按钮的IsEnabled属性绑定到ViewModel上的一个属性

3
我正在构建一个Silverlight应用程序,希望在后台进行长时间运行的操作时禁用按钮。我使用MVVM,在ViewModel中有一个名为SearchInProgress的属性。现在我想在SearchInProgress为true时禁用搜索按钮。在WPF中,我会简单地编写一个DataTrigger,将搜索按钮上的IsEnabled设置为false。
遗憾的是,在Silverlight中无法使用DataTriggers,因此我正在寻找另一种解决方案。我尝试使用VisualStateManager,但没有取得任何进展。VSM对于我想要实现的这个简单事情来说似乎过于复杂了。
感谢任何帮助。
8个回答

7
不必为设置SearchInProgress=true并将IsEnabled=false而做出艰难的努力,为什么不创建一个CanSearch属性,并绑定到它呢?该属性可以是只读的(或者具有私有 setter),而另一个属性则可以代表它触发PropertyChanged事件。
归根结底,视图模型的目的是将逻辑从视图中移除。视图绑定到SearchInProgress(从而使用转换器来对其进行取反以获得IsEnabled)意味着视图了解何时能够进行搜索或无法进行搜索。然而,绑定到CanSearch属性意味着视图模型完全控制何时启用搜索,而视图可以保持简单。
或者,您可以使用Blend中安装的行为API,因为它们与数据触发器类似。

好的,我会研究你提到的两件事情。 - Manuel R.
然而,我想要避免的是在每个我想要禁用或更改搜索进行时的控件上绑定CanSearch属性。如果可能的话,我宁愿将其放在一个地方。 - Manuel R.
我个人更喜欢在每个位置使用XAML绑定,但如果您不这样做,您可以始终在代码后台跟踪“PropertyChanged”事件,并更新每个适用控件的IsEnabled属性。(请记住,如果您更改具有OneWay绑定的属性的值,则会删除绑定) - Richard Szalay

1
一种可能更好的方法是使用来自Prism2的DelegateCommand,将其附加到搜索按钮并在ViewModel中实现其CanExecute方法,以便它返回!SearchInProgress。
然后,当ViewModel启动搜索操作时,它会将SearchInProgress更改为true(以便CanExecute返回false),然后会在命令上调用RaiseCanExecuteChanged(这将导致按钮被禁用)。一旦搜索操作结束,ViewModel就会将SearchInProgress更改回false(以便CanExecute返回true),然后再次调用RaiseCanExecuteChanged(这将导致按钮启用)。

0

绑定到SearchInProgress属性,但通过转换器运行以反转布尔值。

在绑定中,类似于

IsEnabled="{Binding Path=SearchInProgress,Converter={StaticResource YOURCONVERTERHERE}}"

在转换器的转换函数中,我认为应该
return !(value as bool)

无论您想在空项目等方面放置什么样的健全性检查。


我认为这会起作用,但是如果我想在GUI中同时更改几个事物,可能会有点混乱。 - Manuel R.

0

我找到了一个相当满意的解决方案。首先在我的LayoutRoot网格上定义了两个VSM状态:“SearchInProgress”和“Normal”。

        <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="Standard">
            <VisualState x:Name="SearchInProgress">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="SearchButton" Storyboard.TargetProperty="(Control.IsEnabled)">
                        <DiscreteObjectKeyFrame KeyTime="00:00:00">
                            <DiscreteObjectKeyFrame.Value>
                                <system:Boolean>False</system:Boolean>
                            </DiscreteObjectKeyFrame.Value>
                        </DiscreteObjectKeyFrame>
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="SearchCancelButton" Storyboard.TargetProperty="(Control.IsEnabled)">
                        <DiscreteObjectKeyFrame KeyTime="00:00:00">
                            <DiscreteObjectKeyFrame.Value>
                                <system:Boolean>True</system:Boolean>
                            </DiscreteObjectKeyFrame.Value>
                        </DiscreteObjectKeyFrame>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
            <VisualState x:Name="Normal"/>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

非常简单,我可能会进行调整,但它有效。

为了在两种状态之间切换,我使用这里的DataStateBehavior,它让我绑定到数据上下文(视图模型)上的属性,并相应地切换两种状态:

        <interactivity:Interaction.Behaviors>
        <exprsamples:DataStateBehavior Binding="{Binding Path=SearchIsInProgress, Mode=TwoWay}"
                                       Value="True"
                                       TrueState="SearchInProgress"
                                       FalseState="Normal">
        </exprsamples:DataStateBehavior>
    </interactivity:Interaction.Behaviors>

我认为现在我能够充分利用VSM的强大功能、Blend中的可设计性以及“DataTrigger”机制的灵活性。


0

您需要创建一个命令并将其绑定到按钮。您要寻找的所有内容都已集成在运行时中。像MVVM Light和Prism这样的框架使创建新命令变得很容易,但您也可以像这样自己完成:

创建一个实现ICommand的类。给它一个名为SearchInProgress的私有布尔值。当搜索开始时,请将SearchInProgress设置为true。当搜索完成(成功完成或因超时、被中止等而完成)时,请将SearchInProgress设置为false。让ICommand.CanExecute的实现返回!SearchInProgress。在您的视图模型上公开Search ICommand,然后将按钮的命令属性绑定到视图模型上的命令。

伪代码:

public class MySearchCommand : ICommand
{
  public event EventHandler CanExecuteChanged;
  private bool _searching;
  private bool SearchInProgress
  {
    get { return _searching; }
    set
    {
      if (_searching == value) return;
      _searching = value;
      if (CanExecuteChanged != null) CanExecuteChanged(this, EventArgs.Empty);
    }

  public bool CanExecute(object param)
  {  return !SearchInProgress }

  public void Execute(object param)
  {
    try
    {
      SearchInProgress = true;
      // search code here including callback to OnSearchCompleted method
    }
    catch(Exception ex)
    {
      SearchInProgress = false;
    }
  }

  private void OnSearchCompleted(SomeCallbackResult result)
  {
    SearchInProgress = false;
  }
}

public class ViewModel : INotifyPropertyChange
{
  public ICommand SearchCommand { get; private set; }
  public ViewModel()
  {
    SearchCommand = new MySearchCommand();
  }
}

XAML:

<UserControl ....>
  <UserControl.DataContext>
    <ViewModel />
  </UserControl.DataContext>
  <Grid>
    <Button Command={Binding SearchCommand} />
  </Grid>
</UserControl>

0
使用DependencyProperty或INotifyPropertyChanged接口作为数据模型,然后绑定到公共属性SerachInProgress。我认为您可能还需要创建一个转换器,将其转换为布尔值的相反值。

0
我目前能想到的唯一明智的解决方案是,在ViewModel中设置一个名为“SearchCompleted”的事件,让视图进行订阅,当事件触发时,相应地更改视图。

0

完全没有必要为此将PRISM dll添加到您的项目中。 您只需要10行代码和2个单元测试。

不过,您仍然想使用命令模式。 添加一个“Command”附加属性,该属性接受“ICommand”,当设置属性时,您需要:

  1. 观察命令并根据其请求启用或禁用按钮。
  2. 向按钮的“Click”事件添加处理程序,该处理程序调用命令上的“Execute”方法。

关于PRISM的说明:该库很烂! 但是Composite Application Guidance Book对于任何编写MVVM应用程序的人来说都是必读的,并提供有关命令模式的更多信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接