3

这是设置:我有一个自动完成框,它由从 WCF 服务获取数据的视图模型填充。所以到目前为止它非常简单明了。

现在,我正在尝试遵循 MVVM 的原则,即视图模型对视图本身一无所知。这很好,因为我通过 triggers 和 commands将自动完成框的填充事件绑定到我的视图模型的方法。

所以视图模型正在获取数据,而视图正在等待。还没有问题。

现在,视图模型获得了数据,我将结果集合传递给绑定到控件的 ItemSource 属性的属性。屏幕上什么也没有发生。

我去 MSDN 并找到官方批准的方式来处理这种情况(http://msdn.microsoft.com/en-us/library/system.windows.controls.autocompletebox.populating(v=vs .95).aspx ):

  • 将 MinimumPrefixLength 和 MinimumPopulateDelay 属性设置为大于默认值的值,以最大限度地减少对 Web 服务的调用。

  • 处理 Populating 事件并将 PopulatingEventArgs.Cancel 属性设置为 true。

  • 进行必要的处理并将 ItemsSource 属性设置为所需的项目集合。

  • 调用 PopulateComplete 方法向 AutoCompleteBox 发出信号以显示下拉菜单。

现在我看到最后一步的一个大问题,因为我不知道如何从视图模型中调用视图上的方法,前提是他们不知道(也不应该知道!)关于彼此的任何事情。

那么我到底应该如何在不违反 MVVM 原则的情况下从视图模型中调用 PopulateComplete 视图方法呢?

4

1 回答 1

2

如果您使用 Blend 的交互库,一个选项是附加Behavior<T>AutoCompleteBox

public class AsyncAutoCompleteBehavior : Behavior<AutoCompleteBox>
{
    public static readonly DependencyProperty SearchCommandProperty
        = DependencyProperty.Register("SearchCommand", typeof(ICommand), 
              typeof(AsyncAutoCompleteBehavior), new PropertyMetadata(null));

    public ICommand SearchCommand
    {
        get { return (ICommand)this.GetValue(SearchCommandProperty); }
        set { this.SetValue(SearchCommandProperty, value); }
    }

    protected override void OnAttached()
    {
        this.AssociatedObject.Populating += this.PopulatingHook;
    }

    protected override void OnDetaching()
    {
        this.AssociatedObject.Populating -= this.PopulatingHook;
    }

    private void PopulatingHook(object sender, PopulatingEventArgs e)
    {
        var command = this.SearchCommand;
        var parameter = new SearchCommandParameter(
                () => this.AssociatedObject
                          .Dispatcher
                          .BeginInvoke(this.AssociatedObject.PopulateComplete),
                e.Parameter);
        if (command != null && command.CanExecute(parameter))
        {
            // Cancel the pop-up, execute our command which calls
            // parameter.Complete when it finishes
            e.Cancel = true;
            this.SearchCommand.Execute(parameter);
        }
    }
}

使用以下参数类:

public class SearchCommandParameter
{
    public Action Complete
    {
       get;
       private set;
    }

    public string SearchText
    {
       get;
       private set;
    }

    public SearchCommandParameter(Action complete, string text)
    {
        this.Complete = complete;
        this.SearchText = text;
    }
}

此时你需要做两件事:

  1. 连接行为

    <sdk:AutoCompleteBox MinimumPopulateDelay="250" MinimumPrefixLength="2" FilterMode="None">
        <i:Interaction.Behaviors>
            <b:AsyncAutoCompleteBehavior SearchCommand="{Binding Search}" />
        </i:Interaction.Behaviors>
    </sdk:AutoCompleteBox>
    
  2. 创建一个处理您的 aysnc 搜索的 DelegateCommand。

    public class MyViewModel : ViewModelBase
    {
        public ICommand Search
        {
            get;
            private set;
        }
    
        private void InitializeCommands()
        {
            this.Search = new DelegateCommand<SearchCommandParamater>(DoSearch);
        }
    
        private void DoSearch(SearchCommandParameter parameter)
        {
            var client = new WebClient();
            var uri = new Uri(
                @"http://www.example.com/?q="
                + HttpUtility.UrlEncode(parameter.SearchText));
            client.DownloadStringCompleted += Downloaded;
            client.DownloadStringAsync(uri, parameter);
        }
    
        private void Downloaded(object sender, DownloadStringCompletedEventArgs e)
        {
            // Do Something with 'e.Result'
            ((SearchCommandParameter)e.UserState).Complete();
        }
    }
    
于 2012-11-26T17:54:56.773 回答