2013-01-25 92 views
0

我有一个wpv/mvvm-light/vb.net应用程序,具有主/明细视图。在这个视图中,有一个客户列表框和客户详细信息的详细视图,用户可以查看和编辑客户。Mvvm - 取消更改Wpf列表框,vb.net

我想添加一个功能,在列表框中选择新客户端时,系统会提示用户保存更改。如果用户从消息框中选择是,然后保存更改,如果没有,则放弃更改并将先前选择的项目返回到其原始值。我有这一切工作正常。

我的问题是,当用户选择一个新的客户端和消息框要求他们保存更改时,列表框不同步。这意味着列表框会显示所选的新客户端,但详细信息视图仍会显示以前的客户端。奇怪的是,它在罕见的情况下正常工作。

以下是我的观点:

<UserControl x:Class="FTC.View.ClientListView" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:FTC_Application" 
      mc:Ignorable="d" 
      d:DesignHeight="400" d:DesignWidth="900"> 


       <ListBox  
        Grid.Column="1" 
        Width="350"      
        Style="{DynamicResource FTC_ListBox}" 
        ItemTemplate="{DynamicResource FTC_ClientListTemplate}" 
        ItemContainerStyle="{DynamicResource FTC_ListItem}" 
           ItemsSource="{Binding ClientViewSource.View}" 
           SelectedItem="{Binding Path=Selection, Mode=TwoWay}"     
        /> 


        <ContentControl DataContext="{Binding Path=Selection, Mode=TwoWay}" > 
         <!--all the display stuff goes here for the detail view--> 
        </ContentControl> 

</UserControl> 

下面是在列表框的selectedItem属性绑定到视图模型的财产。它也是内容控件的绑定,用于显示细节。

Public Property Selection As client 
      Get 
       Return Me._Selection 
      End Get 
      Set(ByVal value As client) 
       ''capture current value of selection 
       _PreviousClient = _Selection 

       ''If they are the same, 
       If value Is _PreviousClient Then 
        Return 
       End If 

       ' Note that we actually change the value for now.This is necessary because WPF seems to query the 
       ' value after the change. The list box likes to know that the value did change. 
       If Me._Selection.HasChanges = True And _Selection.HasErrors = False Then 
        'If HasChangesPrompt(value) = True Then 
        ' ''user rejects saving changes, exit property 
        ' Return 
        'End If 
        If FTCMessageBox.Show("Do you want to save your changes", "Unsaved Changes", MessageBoxButton.YesNo, MessageBoxImage.Warning) = MessageBoxResult.No Then 
         ''SELECTION IS CANCELLED 
         ' change the value back, but do so after the UI has finished it's current context operation. 
         Application.Current.Dispatcher.BeginInvoke(New Action(Sub() 
                        '' revert the current selected item to its original values and reset its HasCHanges tracking 
                        objHelper.CopyProperties(_OriginalClient, _Selection) 
                        _Selection.HasChanges = False 
                        RaisePropertyChanged(ClientSelectedPropertyName) 
                        ''continue with listbox selection changing to the new value for selection 
                        _ClientCollectionViewSource.View.MoveCurrentTo(value) 
                       End Sub), DispatcherPriority.Normal, Nothing) 
         Return 
        Else 
         ''save changes to database 
         SaveExecute() 
        End If 
       End If 

       _Selection = value 

       _Selection.HasChanges = False 
       RaisePropertyChanged(ClientSelectedPropertyName) 

       ''clone the unchanged version of the current selected client on na original variable 
       objHelper.CopyProperties(_Selection, _OriginalClient) 

      End Set 
     End Property 

这样的想法是,如果用户不希望保存更改,客户端的原始值复制(使用反射)在当前值,则UI被更新,选择继续上用户选择的新值。但是,就像我上面所说的,列表框不反映这种变化,即使我累了与以下行硬编码:

''continue with listbox selection changing to the new value for selection 
_ClientCollectionViewSource.View.MoveCurrentTo(value) 

我通过努力定制的解决方案得到了这个解决方案发布HERE

能任何人都可以帮我弄清楚为什么当这种情况发生时我的列表框不同步。

在此先感谢

回答

0

所以我有一个工作的例子,我认为遵循MVVM-Light标准。有很多事情要做,所以我会尽量保持简洁精确。

我结束了用ListView(而不是ListBox)绑定到SelectionChanged事件的EventToCommand。 EventToCommand需要新的名称空间引用,如下所示。然后,我将EventToCommand绑定到视图模型中的RelayCommand,该模型又调用一个处理客户端验证的私有子组件,并根据需要保存/取消/更新listview seleceditem。

有关更多信息,我有一个导航服务用于在我的wpf应用程序中的视图之间进行导航。我使用MVVM-Light信使发送了一个导航启动的消息,该消息由此视图模型“接收”。然后执行相同的客户端验证功能,并根据用户对抛出的对话消息的响应取消/允许导航。除非有要求,否则我不会包含所有的导航代码。以下是解决我原来的问题所需的代码。

<UserControl x:Class="FTC.View.ClientListView" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:FTC_Application" 
      xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
      xmlns:cmd="http://www.galasoft.ch/mvvmlight" 
      mc:Ignorable="d" 
      d:DesignHeight="400" d:DesignWidth="900"> 

       <ListView  
        Grid.Column="1" 
        Width="350"      
        Style="{DynamicResource FTC_ListView}" 
        ItemTemplate="{DynamicResource FTC_ClientListTemplate}" 
        ItemContainerStyle="{DynamicResource FTC_ListViewItem}" 
        ItemsSource="{Binding ClientViewSource.View}" 
        SelectedItem="{Binding Path=Selection, Mode=TwoWay}"> 
        <i:Interaction.Triggers> 
         <i:EventTrigger EventName="SelectionChanged"> 
          <cmd:EventToCommand Command="{Binding SelectedItemChangedCommand}"/> 
         </i:EventTrigger> 
        </i:Interaction.Triggers> 
       </ListView> 

        <ContentControl DataContext="{Binding Path=Selection, Mode=TwoWay}" > 
         <!-- Display stuff and bound controls go here --> 
        </ContentControl> 


    </Grid> 
</UserControl> 

再下面是相关的代码在我的视图模型(我删除尽可能多的代码尽可能地保持清晰):

Imports System.Data 
Imports System.ComponentModel 
Imports System.Collections.ObjectModel 
Imports System.Windows.Threading 

Imports GalaSoft.MvvmLight 
Imports GalaSoft.MvvmLight.Command 
Imports GalaSoft.MvvmLight.Messaging 

Imports FTCModel 
Imports FTC_Application.FTC.Model 
Imports FTC_Application.FTC.View 
Imports FTC_Application.FTC.ViewModel 
Imports FTC_Application.FTC.MessageBox 
Imports FTC_Application.FTC.Helpers 
Imports FTC_Application.FTC.MessengerHelper 

Namespace FTC.ViewModel 
    Public Class ClientListViewModel 
     Inherits ViewModelBase 
     Implements IDataErrorInfo 

#Region "DECLARATIONS" 

     Public Const ClientCollectionPropertyName As String = "ClientCollection" 
     Public Const ClientSelectedPropertyName As String = "Selection" 
     Public Const ClientDetailCollectionPropertyName As String = "ClientDetailCollection" 
     Public Const ClientPropertyName As String = "Client" 

     ''gets the data from LINQ to ENT Model 
     Private _Clients As New ObservableCollection(Of client) 
     ''creats holder for the selected item two way binding 
     Private _Selection As New client 
     ''the following is used to track changes for unding and canceling selection changed 
     Private _PreviousClient As New client 
     Private _PreviousOriginalClient As New client 
     Private _OriginalClient As New client 

     ''Recieves observable collection and provicdes sorting and filtering function 
     Private _ClientCollectionViewSource As New CollectionViewSource 

     ''RELAY COMMANDS declarations 
     Private _SaveCommand As RelayCommand 
     Private _SelectedItemChangedCommand As RelayCommand 

     ''gets the VML for getting the data service 
     Private vml As ViewModelLocator = TryCast(Application.Current.Resources("Locator"), ViewModelLocator) 
     ''this is a holder for the client data service 
     Private _ClientAccess As IClientDataService = vml.Client_Service 

     '' has functions using reflection for copying objects 
     Dim objHelper As New ObjectHelper 

     ''tracks if client validation is coming from navigation or listview selecteditemchanged 
     Private bNavigatingFlag As Boolean = False 

#End Region 

#Region "PROPERTIES" 

     Public ReadOnly Property ClientViewSource As CollectionViewSource 
      Get 
       Return Me._ClientCollectionViewSource 
      End Get 
     End Property 
     Private Property Clients As ObservableCollection(Of client) 
      Get 
       Return Me._Clients 
      End Get 
      Set(ByVal value As ObservableCollection(Of client)) 
       Me._Clients = value 

       _Clients = value 
       RaisePropertyChanged(ClientCollectionPropertyName) 

      End Set 
     End Property 
     Public Property Selection As client 
      Get 
       Return Me._Selection 
      End Get 
      Set(ByVal value As client) 
       ''capture current value of selection 
       _PreviousClient = _Selection 
       objHelper.CopyProperties(_OriginalClient, _PreviousOriginalClient) 

       ''If they are the same, 
       If value Is _PreviousClient Then 
        Return 
       End If 

       _Selection = value 
       _Selection.HasChanges = False 
       RaisePropertyChanged(ClientSelectedPropertyName) 
       ''clone the unchanged version of the current selected client on na original variable 
       objHelper.CopyProperties(_Selection, _OriginalClient) 

      End Set 
     End Property 

#End Region 

#Region "COMMANDS" 

     Public ReadOnly Property SelectedItemChangedCommand() As RelayCommand 
      Get 
       If _SelectedItemChangedCommand Is Nothing Then 
        _SelectedItemChangedCommand = New RelayCommand(AddressOf SelectionChangedValidate) 
       End If 
       Return _SelectedItemChangedCommand 
      End Get 
     End Property 

#End Region 

#Region "METHODS" 

     Private Sub SelectionChangedValidate() 

      ''Uses falg to tell if validation request triggered by navigation event or listview selecteditemchanged event 
      ''use previous client for listview event and current client for navigating event 
      Dim _ClientToValidate As client 
      If bNavigatingFlag = True Then 
       _ClientToValidate = _Selection 
      Else 
       _ClientToValidate = _PreviousClient 
      End If 

      If _ClientToValidate.HasChanges = True And _ClientToValidate.HasErrors = False Then 
       Dim message = New DialogMessage(_ClientToValidate.chrCompany.ToString + " has been changed." + vbCrLf + "Do you want to save your changes?", AddressOf SavePreviousResponse) With { _ 
        .Button = MessageBoxButton.YesNo, _ 
        .Caption = "Unsaved Changes" _ 
       } 
       Messenger.[Default].Send(message) 
       Exit Sub 
      End If 

      If _ClientToValidate.HasErrors = True Then 
       Dim message = New DialogMessage(_ClientToValidate.chrCompany.ToString + " has errors." + vbCrLf + "You must correct these errors before you can continue.", AddressOf HasErrorsResponse) With { _ 
        .Button = MessageBoxButton.OK, _ 
        .Caption = "Validation Error" _ 
       } 
       Messenger.[Default].Send(message) 
       Exit Sub 
      End If 

      ''reset the navigation flag 
      bNavigatingFlag = False 

     End Sub 
     Private Sub SavePreviousResponse(result As MessageBoxResult) 
      If result = MessageBoxResult.No Then 
       objHelper.CopyProperties(_PreviousOriginalClient, _PreviousClient) 
       _PreviousClient.HasChanges = False 
      Else 
       ''user wants to save changes, save changes to database 
       SaveExecute() 
      End If 
     End Sub 
     Private Sub HasErrorsResponse(result As MessageBoxResult) 
      Selection = _PreviousClient 
      ''_ClientCollectionViewSource.View.MoveCurrentTo(_PreviousClient) 
     End Sub 
     Private Function HasChangesPrompt(value As client) As Boolean 
      If FTCMessageBox.Show("Do you want to save your changes", "Unsaved Changes", MessageBoxButton.YesNo, MessageBoxImage.Warning) = MessageBoxResult.No Then 
       '' change the selected client back to its original value, but do so after the UI has finished its current context operation. 
       Application.Current.Dispatcher.BeginInvoke(New Action(Sub() 
                      '' revert the current selected item to its original values and reset its HasCHanges tracking 
                      objHelper.CopyProperties(_OriginalClient, _Selection) 
                      _Selection.HasChanges = False 
                      RaisePropertyChanged(ClientSelectedPropertyName) 
                      ''continue with listbox selection changing to the new value for selection 
                      _ClientCollectionViewSource.View.MoveCurrentTo(value) 
                     End Sub), DispatcherPriority.Normal, Nothing) 
       Return True 
      Else 
       ''user wants to save changes, save changes to database 
       Return False 
       SaveExecute() 
      End If 
     End Function 

#End Region 

     Public Sub New() 

      Clients = _ClientAccess.GetClient_All 

      ''Sets the observable collection as the source of the CollectionViewSource 
      _ClientCollectionViewSource.Source = Clients 

      If Selection.idClient = 0 Then 
       Selection = Clients.Item(0) 
      End If 

      ''register for messages 
      Messenger.[Default].Register(Of String)(Me, AddressOf HandleMessage) 

     End Sub 

    End Class 

End Namespace 

INXS,你会发现,选择属性setter有少得多的代码/逻辑。另外,我认为视图模型的每个部分都是可测试的,我的视图和视图模型之间没有直接的耦合。但这是我的第一个WPF/MVVM应用程序,所以我不能完全掌握所有的概念。

我希望这可以帮助某人,因为它花了我相当长的一段时间才弄明白。

2

第一: 我无法找到你的解决方案,真正的问题,但你必须definitly - 我重复 - 在您的属性setter definitly太多的代码和逻辑。尝试将其移至其他方法并验证您的许多“其他”块的实现。

第二个: 只有当您在列表框中选择一个新项目时,Setter才会被触发,但您为'ClientSelectedPropertyName'而不是'Selection'提升属性更改为其应有的。将属性改为等于你的设定者的末尾。

试试这个。我希望它有助于:)

+0

感谢您的回复。我对setter中的代码量也感到不舒服。但我试图遵循MVVM模式,因此UI视图需要与ViewModel分离。所以我不能在view.xaml对象的事件后面使用简单的代码。因此,我必须在视图模型代码中管理这些,并且在选择更改时执行的唯一代码是选择对象的设置器。所以我把逻辑放在那里取消那个改变。你知道另一种方式,我可以做到这一点。 –