0

マスター/詳細ビューを備えた wpv/mvvm-light/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>

以下は、リストボックスの選択された項目がバインドされているビューモデルのプロパティです。詳細を表示するコンテンツ コントロールのバインドでもあります。

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)

ここに投稿されたソリューションをカスタマイズすることで、このソリューションを取得しまし

これが発生したときにリストボックスが同期しなくなる理由を誰かが理解するのを手伝ってくれますか?

前もって感謝します

4

2 に答える 2

2

まず、あなたのソリューションで本当の問題を見つけることができませんが、Property Setter のコードとロジックが明らかに多すぎます。それを他のメソッドに移動して、それらの多くの「if else」ブロックの実装を検証してください。

2 番目: Setter は、リストボックスで新しいアイテムを選択したときにのみ起動されますが、「ClientSelectedPropertyName」のプロパティの変更が発生し、「Selection」のプロパティは変更されません。常に変更されたプロパティをセッターの最後に移動します。

これを試して。私はそれが役立つことを願っています:)

于 2013-01-25T07:16:18.917 に答える
0

そのため、MVVM-Light 標準に準拠していると思われる実例があります。たくさんのことが起こっているので、私はそれを短く正確に保つように努めます.

ListView(リストボックスではなく)でSelectionChangedイベントにバインドされたEventToCommandを使用することになりました。以下に示すように、EventToCommand には新しい名前空間参照が必要でした。次に、EventToCommand をビュー モデルの RelayCommand にバインドしました。これにより、クライアントの検証を処理し、必要に応じてリストビューの選択項目を保存/キャンセル/更新するプライベート サブルーチンが呼び出されます。

詳細については、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 を使用すると、selection プロパティ セッターのコード/ロジックがはるかに少ないことに気付くでしょう。また、ビューモデルの各部分はテスト可能であり、ビューとビューモデルの間に直接の結合はないと思います。しかし、これは私の最初の WPF/MVVM アプリケーションであるため、すべての概念を完全に把握しているわけではありません。

それを理解するのにかなりの時間がかかったので、これが誰かを助けることができることを願っています.

于 2013-01-25T19:07:44.740 に答える