2013-01-25 3 views
0

master/detail보기가있는 wpv/mvvm-light/vb.net 응용 프로그램이 있습니다. 이보기에는 클라이언트의 목록 상자와 고객이 클라이언트를보고 편집 할 수있는 클라이언트 세부 정보의 상세보기가 있습니다.Mvvm - Wpf Listbox, 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 

SO 아이디어는 사용자가 변경 사항을 저장하지 않을 경우, 클라이언트의 원래 값은 다음 UI가 업데이트되어, 현재 값보다 (반사를 사용하여) 복사되어 있고 선택에에 계속 사용자가 선택한 새 값 제가 위에서 말한 것처럼하지만, 목록 상자 내가 다음 줄에 하드 코드를 피곤하더라도이 변경 내용을 반영하지 않습니다

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

내가 솔루션을 사용자 정의하는 작업으로이 솔루션을 가지고 게시 HERE

수 누구든지 내 목록 상자가 동기화되지 않는 이유를 알아내는 데 도움이됩니다. 사전

먼저

답변

0

MVVM-Light 표준을 따르는 작업 예제가 있습니다. 많은 일들이 진행되고 있으므로 짧고 정확하게 유지하려고 노력할 것입니다.

목록 상자 대신 ListView를 사용하여 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> 

그럼 다음은 관련 코드는 내보기 모델 (I 명확를 유지하기 위해 가능한 한 많은 코드를 제거) :

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 - 그리고 내가 반복 - definitly 너무 많은 코드와 로직을 귀하의 재산 세터에. 다른 방법으로 이동하여 그 많은 'if else '블록의 구현을 검증하십시오.

두 번째 : Setter는 목록 상자에서 새 항목을 선택할 때만 시작되지만 'ClientSelectedPropertyName '에 대해서는 속성 변경을 발생시키고 'Selection'에 대해서는 변경하지 않습니다. 변경된 alsways 속성을 세터 끝에 이동하십시오.

시도해보십시오. 도움이되기를 바랍니다 :)

+0

답장을 보내 주셔서 감사합니다. 나는 세터의 코드 양에 대해서도 불편하다. 그러나 MVVM 패턴을 따르려고하므로 UI보기를 ViewModel에서 분리해야합니다. 그래서 view.xaml 객체의 이벤트 뒤에 간단한 코드를 사용할 수 없습니다. 따라서 뷰 모델 코드에서이를 관리해야하는 방법과 선택 항목이 변경 될 때 실행되는 유일한 코드는 선택 개체에 대한 설정자입니다. 그래서 나는 그 변화를 취소하기위한 논리를 그 안에 넣었습니다. 내가 할 수있는 또 다른 방법을 안다. –