3

我遇到了从我的视图模型引发的问题事件,有时在服务视图中将数据上下文显示为空。我开始认为这是弱绑定模式问题,并且我没有使用它或者我误解了它(一般来说是设计模式的新手,几个月前开始 WPF)。

相关 MainWindow.xaml

<Grid Tree:TreeView.ItemSelected="DisplayRequested"
      Tree:TreeView.PoolCategoriesChanged="PoolContentChanged">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="200"/>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <GridSplitter Grid.Column="0" VerticalAlignment="Stretch"/>
    <GridSplitter Grid.Column="1" VerticalAlignment="Stretch"
                  Background="Gray" ShowsPreview="True" Width="5"
                  HorizontalAlignment="Center"/>
    <GridSplitter Grid.Column="2" VerticalAlignment="Stretch"/>
    <Tree:TreeView Name="poolTree" Grid.Column="0" Focusable="True" />
    <ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto">
        <Details:DetailsView Name="detailsDisplay" Focusable="True"/>
    </ScrollViewer>
</Grid>

后面的相关代码

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var vm = (CLUViewModel)e.NewValue;
        if (vm == null) return;
        vm.OwnerCleanupStarted += OnOwnerCleanupStarting;
        vm.OwnerCleanupEnded += OnOwnerCleanupEnding;
    }

    #region Event Handlers
    private void OnOwnerCleanupEnding(object sender, EventArgs e)
    {
        ViewServices.CloseProgressDialog();
    }

    private void OnOwnerCleanupStarting(object sender, EventArgs e)
    {
        var vm = DataContext as CLUViewModel;
        if (vm == null) return;
        var progressDialogViewModel = vm.ProgressDialogVM;
        ViewServices.ShowProgressDialog(GetWindow(this), progressDialogViewModel);
    }
    #endregion
}

我有几个RoutedEvents似乎没有问题的功能。不过,该OnOwnerCleanupStarting事件似乎在 var 上返回了很多 null vm = DataContext as CLUViewModel;。这是因为它绑定得太强并且没有使用WPF框架吗?

如果我把它放在调试和跟踪中,它总是可以工作的,并且在正常使用过程中很多时候都可以正常工作。这是一种竞争条件,我在视图中使用内存中的侦听器,当焦点到子组件时,该视图未初始化?

来自VM的调用逻辑:

public class CLUViewModel : ViewModelBase
{
    #region Properties
    private RelayCommand _manageOwnersDialogCommand;

    public ProgressDialogViewModel ProgressDialogVM;
    #endregion

    public CLUViewModel()
    {
        ProgressDialogVM = new ProgressDialogViewModel(string.Empty);
    }

    #region ManageOwnersDialogCommand
    public ICommand ManageOwnersDialogCommand
    {
        get
        {
            return _manageOwnersDialogCommand ??
                   (_manageOwnersDialogCommand = new RelayCommand(param => OnManageOwnersDialogShow()));
        }
    }

    private void OnManageOwnersDialogShow()
    {
        var dialog = new ManageOwnersDialog();
        var vm = new ManageOwnersViewModel();
        dialog.DataContext = vm;

        if (!dialog.ShowDialog().Value) return;

        var ownersRequiringCleanup = GetOwnersRequiringCleanup(vm);

        if(ownersRequiringCleanup.Count < 1) return;

        ProgressDialogVM.ClearViewModel();
        ProgressDialogVM.TokenSource = new CancellationTokenSource();
        ProgressDialogVM.ProgressMax = ownersRequiringCleanup.Count*2;

        RaiseOwnerCleanupStartedEvent();

        var taskOne = Task.Factory.StartNew(() => OwnerCleanupService.DoOwnerCleanup(ownersRequiringCleanup, ProgressDialogVM));

        taskOne.ContinueWith(t => RaiseOwnerCleanupEndedEvent(), TaskScheduler.FromCurrentSynchronizationContext());
    }

    private List<Owner> GetOwnersRequiringCleanup(ManageOwnersViewModel vm)
    {
        var ownersRequiringCleanup = new List<Owner>();

        // using DB to determine cleanup
        // Proprietary code removed for this discussion

        return ownersRequiringCleanup;
    }
    #endregion

    #region Events
    public event EventHandler OwnerCleanupStarted;
    public event EventHandler OwnerCleanupEnded;

    public void RaiseOwnerCleanupStartedEvent()
    {
        if (OwnerCleanupStarted == null) return;            
        OwnerCleanupStarted(this, new EventArgs());
    }

    public void RaiseOwnerCleanupEndedEvent()
    {
        if (OwnerCleanupEnded == null) return;
        OwnerCleanupEnded(this, new EventArgs());
    }
    #endregion
}

我对其他几个控件也有同样的问题,它们的各种 VM 调用父级(在树视图中)并且父级引发事件。

我一直在学习这一点,我在 VM 端使用的一些事件是我早期对事物如何工作的理解。这是我应该在哪里调用一个引发到我的视图的事件,然后RountedEvent将气泡启动到适当的级别?我是否陷入了强绑定与弱绑定的陷阱?

编辑:解决了正在看到的父/子 TreeView 问题。Master-Detail 模式意味着我一直在访问不可见或从未加载的详细视图。现在最初的问题仍然是出现空值。对于 View DataContext 不会遇到困难的 UI 相关问题,是否有更好的方法从 VM 回调到 View?

4

1 回答 1

1

我建议不要在 WPF 中使用基于事件的编程模型。因为进入这样的事情太容易了。基于事件的编程强制紧密耦合。所以这是一个双重禁忌

不是在 View 中抛出事件,而是使用DelegateCommand来执行在 ViewModel 级别中定义的动作,而不是在 ViewModel 中抛出事件,只需使用常规属性和INotifyPropertyChanged来表示事物的状态。

这当然需要从 winforms 或其他任何非 wpf 思维方式进行重大转变,但是当您意识到这一点时,您会产生更少和更清晰的代码。

于 2012-11-18T04:29:07.543 回答