2

我目前正在开发 Windows Phone 应用程序,我想使用 Reactive Extensions 来创建异步以获得更好的 UI 体验。

我使用 MVVM 模式:我的 View 在我的 ViewModel 中有一个 ListBox 绑定到 Items 的 ObservableCollection。项目具有传统属性,如名称或 IsSelected。

<ListBox SelectionMode="Multiple" ItemsSource="{Binding Checklist, Mode=TwoWay}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

我在 App.xaml.cs 中实例化我的 MainViewModel:

public partial class App : Application
{
    private static MainViewModel _viewModel = null;

    public static MainViewModel ViewModel
    {
        get
        {
            if (_viewModel == null)
                _viewModel = new MainViewModel();

            return _viewModel;
        }
    }

    [...]
}

我在 MainPage_Loaded 中加载我的数据。

public partial class MainPage : PhoneApplicationPage
{
   public MainPage()
    {
        InitializeComponent();
        this.DataContext = App.ViewModel;
        this.Loaded += new System.Windows.RoutedEventHandler(MainPage_Loaded);
    }

    private void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
    {
        App.ViewModel.LoadData();
    }
}

我从 ViewModel 加载数据(稍后我将把代码移到模型中):

public class MainViewModel : ViewModelBase
{
    public ObservableCollection<Item> _checklist;

    public ObservableCollection<Item> Checklist
    {
        get
        {
            return this._checklist;
        }
        set
        {
            if (this._checklist != value)
            {
                this._checklist = value;
            }
        }
    }

    private const string _connectionString = @"isostore:/ItemDB.sdf";

    public void LoadData()
    {
        using (ItemDataContext context = new ItemDataContext(_connectionString))
        {
            if (!context.DatabaseExists())
            {
                // Create database if it doesn't exist
                context.CreateDatabase();
            }

            if (context.Items.Count() == 0)
            {
                [Read my data in a XML file for example]

                // Save changes to the database
                context.SubmitChanges();
            }

            var contextItems = from i in context.Items
                                select i;

            foreach (Item it in contextItems)
            {
                this.Checklist.Add(it);
            }
        }
    }

    [...]
}

它工作正常,项目在视图中更新。

现在我想创建异步。在我从 View 而不是 LoadData 调用的新方法中使用传统的 BeginInvoke 可以正常工作。

public partial class MainPage : PhoneApplicationPage
{
    [...]

    private void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
    {
        App.ViewModel.GetData();
    }
}

我使用了一个名为 CurrentDispatcher 的属性,它在 App.xaml.cs 中填充了 App.Current.RootVisual.Dispatcher。

public class MainViewModel : ViewModelBase
{
    [...]

    public Dispatcher CurrentDispatcher { get; set; }

    public void GetData()
    {
        this.CurrentDispatcher.BeginInvoke(new Action(LoadData));
    }

    [...]
}

但我想使用反应式扩展。因此,我尝试了 Rx 的不同元素,例如 ToAsync 或 ToObservable,但是当我将项目添加到清单时,我遇到了一些“UnauthorizedAccessException 未处理”和“无效的跨线程访问”。

我尝试观察其他线程,因为错误可能来自 UI 和后台线程之间的混合,但它不起作用。也许我不像在那种特殊情况下那样使用 Rx?

任何帮助将不胜感激。

回答后编辑:

这是一个很好用的代码!

public void GetData()
{
    Observable.Start(() => LoadData())
    .ObserveOnDispatcher()
    .Subscribe(list =>
    {
        foreach (Item it in list)
        {
            this.Checklist.Add(it);
        }
    });
}

public ObservableCollection<Item> LoadData()
{
    var results = new ObservableCollection<Item>();

    using (ItemDataContext context = new ItemDataContext(_connectionString))
    {
        //Loading

        var contextItems = from i in context.Items
                           select i;

        foreach (Item it in contextItems)
        {
            results.Add(it);
        }
    }

    return results;
}

如您所见,我以前没有正确使用。现在我可以使用 ObservableCollection 并在订阅中使用它。这是完美的!非常感谢!

4

2 回答 2

3

您的代码中似乎没有异步性(保存 BeginInvoke,我不认为它正在做您认为它正在做的事情)。

我假设您想使用 Rx,因为您的代码此刻全部阻塞,并且在建立与数据库的连接并加载数据时 UI 无响应 :-(

你想要做的是在后台线程上执行“繁重的工作”,然后一旦你有了值,只需将它们添加到 Dispatcher 上的 ObservableCollection 中。您可以通过以下三种方式之一执行此操作:

  1. 一口气归还整个系列。然后,您使用添加到 ObservableCollection 的 foreach 循环遍历列表。如果列表太大,这可能会阻止 UI(无响应的应用程序)
  2. 一次返回集合一个值,在对调度程序的独立调用中将每个项目添加到 ObservableCollection。这将使 UI 保持响应,但可能需要更长的时间才能完成
  3. 以缓冲块的形式返回集合并尝试两全其美

您想要的代码可能如下所示

public IList<Item> FetchData()
{
  using (ItemDataContext context = new ItemDataContext(_connectionString))
  {
    //....
    var results = new List<Item>();
    foreach (Item it in contextItems)
    {
      results.Add(it);
    }
    return results;
  }
}
public void LoadData()
{
  Observable.Start(()=>FetchData())
            .ObserveOnDispatcher()
            .Subscribe(list=>
              {
                foreach (Item it in contextItems)
                {
                  this.Checklist.Add(it);
                }
              });
}

该代码不是单元测试的理想选择,但它似乎对您不感兴趣(静态成员、带有 DB 连接的 VM 等),所以这可能行得通?!

于 2012-07-25T09:21:46.487 回答
0

我自己也遇到了这个。有多种方法可以到达调度程序,我不得不使用以下方法。我直接从我的 ViewModels 调用它(没有从外部传入)。

Application.Current.Dispatcher.Invoke(action);
于 2012-07-24T23:03:30.923 回答