简体   繁体   English

WPF MVVM:DataGrid的SelectedItem的显示视图

[英]WPF MVVM: Display View for DataGrid's SelectedItem

I'm new to MVVM and WPF and have been completely hung up on this issue for some time now. 我是MVVM和WPF的新手,并且已经完全挂了一段时间了。 I'm trying to display a View (UserControl) based on the SelectedItem in a DataGrid. 我正在尝试基于DataGrid中的SelectedItem显示视图(UserControl)。 The UserControl renders with the data set in the constructor, but never updates. UserControl使用构造函数中的数据集进行渲染,但永远不会更新。

在此处输入图片说明

I would really appreciate some insight from someone with experience in this. 我真的很感谢有经验的人的一些见解。 I tried adding a Mediator via setUpdateCallback and now the first row in the datagrid updates with the values of the other rows I click on, but this obviously isn't what I want, I need those updates to happen in the separate client view outside of the datagrid. 我尝试通过setUpdateCallback添加一个Mediator,现在datagrid的第一行更新了我单击的其他行的值,但这显然不是我想要的,我需要这些更新在外部的单独客户端视图中进行数据网格。

ClientPanel.xaml ClientPanel.xaml

<UserControl x:Class="B2BNet.View.ClientPanel" 
         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:VM="clr-namespace:B2BNet.ViewModel"
         xmlns:V="clr-namespace:B2BNet.View"
         xmlns:local="clr-namespace:B2BNet"
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         mc:Ignorable="d">
<Grid>
    <Grid.DataContext>
        <VM:ClientPanel/>
    </Grid.DataContext>
    <TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="43" Width="280" Text="{Binding Title}" FontSize="36" FontFamily="Global Monospace"/>
    <DataGrid AutoGenerateColumns="False" x:Name="dataGrid" HorizontalAlignment="Left" Margin="10,60,0,10" VerticalAlignment="Top" ItemsSource="{Binding Clients}" SelectedItem="{Binding currentClient}">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding name}"></DataGridTextColumn>
            <DataGridTextColumn Header="Active" Binding="{Binding active}"></DataGridTextColumn>
            <DataGridTextColumn Header="Status" Binding="{Binding status}"></DataGridTextColumn>
        </DataGrid.Columns>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <i:InvokeCommandAction Command="{Binding clientSelectionChanged_command}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </DataGrid>
    <V:Client HorizontalAlignment="Left" Margin="163,58,-140,0" VerticalAlignment="Top" Content="{Binding currentClient}" Height="97" Width="201"/>
</Grid>

Client.xaml Client.xaml

<UserControl x:Class="B2BNet.View.Client"
         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:VM="clr-namespace:B2BNet.ViewModel"
         xmlns:local="clr-namespace:B2BNet"
         mc:Ignorable="d">
<Grid>
    <Grid.DataContext>
        <VM:Client/>
    </Grid.DataContext>
    <Label x:Name="name" Content="Name:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
    <Label x:Name="active" Content="Active:" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="9,41,0,0"/>
    <Label x:Name="status" Content="Status:" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,72,0,0"/>
    <Label x:Name="namevalue" HorizontalAlignment="Left" Margin="59,10,0,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding name}"/>
    <Label x:Name="activevalue" HorizontalAlignment="Left" Margin="59,41,0,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding active}"/>
    <Label x:Name="statusvalue" HorizontalAlignment="Left" Margin="60,67,-1,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding status}"/>

</Grid>

ViewModel - ClientPanel.cs ViewModel-ClientPanel.cs

using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using B2BNet.MVVM;

namespace B2BNet.ViewModel
{
    public class ClientPanel : ObservableObject
    {
        public string Title { get; set; }

        private Client _currentClient;
        public Client currentClient
        {
            get
            {
                return _currentClient;
            }
            set
            {
                _currentClient = value;
                RaisePropertyChangedEvent( "currentClient" );
                Utility.print("currentClient Changed: " + _currentClient.name);
                Mediator.Instance.Notify(ViewModelMessages.UpdateClientViews, currentClient);
            }
        }

        private ObservableCollection<Client> _Clients;
        public ObservableCollection<Client> Clients
        {
            get
            {
                return _Clients;
            }
            set
            {
                _Clients = value;
                RaisePropertyChangedEvent("Clients");
                Utility.print("Clients Changed");
            }
        }


        public ClientPanel()
        {
            Title = "Clients";
            Clients = new ObservableCollection<Client>();

            for ( int i = 0; i < 10; i++ )
            {
                Clients.Add( new Client { name = "Client-" + i, status = "Current", active = true } );
            }
            currentClient = Clients.First();
            currentClient.setUpdateCallback();
        }

        ////////////
        //COMMANDS//
        ////////////
        public ICommand clientSelectionChanged_command
        {
            get { return new DelegateCommand( clientSelectionChanged ); }
        }
        private void clientSelectionChanged( object parameter )
        {
            B2BNet.Utility.print("clientSelectionChanged");
        }
    }
}

ViewModel - Client.cs ViewModel-Client.cs

using System;
using B2BNet.MVVM;

namespace B2BNet.ViewModel
{
    public class Client : ObservableObject
    {

        private String _name;
        public String name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                RaisePropertyChangedEvent( "name" );
                Utility.print("Client.name changed: " + value );
            }
        }

        private Boolean _active;
        public Boolean active
        {
            get
            {
                return _active;
            }
            set
            {
                _active = value;
                RaisePropertyChangedEvent( "active" );
                Utility.print("Client.active changed" + value );
            }
        }

        private String _status;
        public String status
        {
            get
            {
                return _status;
            }
            set
            {
                _status = value;
                RaisePropertyChangedEvent( "status" );
                Utility.print("Client.status changed" + value );
            }
        }

        public Client()
        {
            name = "Set in Client Constuctor";
            status = "Set in Client Constructor";
        }

        public void setUpdateCallback()
        {
            ////////////
            //Mediator//
            ////////////
            //Register a callback with the Mediator for Client
            Mediator.Instance.Register(
                (Object o) =>
                {
                    Client client = (Client)o;
                    name = client.name;
                    active = client.active;
                    status = client.status;
                }, B2BNet.MVVM.ViewModelMessages.UpdateClientViews
            );
        }
   }
}

Very Basic MVVM Framework 非常基本的MVVM框架

ObservableObject.cs ObservableObject.cs

using System.ComponentModel;

namespace B2BNet.MVVM
{
    public class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChangedEvent( string propertyName )
        {
            var handler = PropertyChanged;
            if ( handler != null )
                handler( this, new PropertyChangedEventArgs( propertyName ) );
        }
    }
}

Mediator.cs Mediator.cs

using System;
using B2BNet.lib;

namespace B2BNet.MVVM
{
    /// <summary>
    /// Available cross ViewModel messages
    /// </summary>
    public enum ViewModelMessages { UpdateClientViews = 1,
                                    DebugUpdated = 2
                                   };


    public sealed class Mediator
    {
        #region Data
        static readonly Mediator instance = new Mediator();
        private volatile object locker = new object();

        MultiDictionary<ViewModelMessages, Action<Object>> internalList
            = new MultiDictionary<ViewModelMessages, Action<Object>>();
        #endregion

        #region Ctor
        //CTORs
        static Mediator()
        {


        }

        private Mediator()
        {

        }
        #endregion

        #region Public Properties

        /// <summary>
        /// The singleton instance
        /// </summary>
        public static Mediator Instance
        {
            get
            {
                return instance;
            }
        }

        #endregion

        #region Public Methods
        /// <summary>
        /// Registers a callback to a specific message
        /// </summary>
        /// <param name="callback">The callback to use 
        /// when the message it seen</param>
        /// <param name="message">The message to 
        /// register to</param>
        public void Register(Action<Object> callback, 
            ViewModelMessages message)
        {
            internalList.AddValue(message, callback);
        }


        /// <summary>
        /// Notify all callbacks that are registed to the specific message
        /// </summary>
        /// <param name="message">The message for the notify by</param>
        /// <param name="args">The arguments for the message</param>
        public void Notify(ViewModelMessages message, 
            object args)
        {
            if (internalList.ContainsKey(message))
            {
                //forward the message to all listeners
                foreach (Action<object> callback in 
                    internalList[message])
                        callback(args);
            }
        }
        #endregion    
    }
}

DelegateCommand.cs DelegateCommand.cs

using System;
using System.Windows.Input;

namespace B2BNet.MVVM
{
    public class DelegateCommand : ICommand
    {
        private readonly Action<object> _action;

        public DelegateCommand( Action<object> action )
        {
            _action = action;
        }

        public void Execute( object parameter )
        {
            _action( parameter );
        }

        public bool CanExecute( object parameter )
        {
            return true;
        }

        #pragma warning disable 67
        public event EventHandler CanExecuteChanged;
        #pragma warning restore 67
    }
}

THE SOLUTION 解决方案

The solution was a simple one. 解决方案很简单。 Thanks for the quick response Rachel :). 感谢Rachel的快速回复:)。 I simple removed the following from each of the Views: 我简单地从每个视图中删除了以下内容:

<!--<Grid.DataContext>
        <VM:ClientPanel/>
    </Grid.DataContext>-->

<!--<Grid.DataContext>
        <VM:Client/>
    </Grid.DataContext>-->

and it works perfectly, even allowing me to update the datagrid: 并且它运行完美,甚至允许我更新数据网格:

在此处输入图片说明

The problem is you are hardcoding the DataContext in your UserControls. 问题是您要在UserControls中对DataContext进行硬编码。

So your Client UserControl has it's DataContext hardcoded to a new instance of Client , and it is NOT the same instance that your ClientPanel UserControl is using. 因此,您的Client UserControl已将其DataContext硬编码为Client的新实例,并且与ClientPanel UserControl使用的实例不同。 So the <V:Client Content="{Binding currentClient}" binding is pointing to a different instance of the property than the one the DataGrid is using. 因此, <V:Client Content="{Binding currentClient}"绑定指向的属性实例不同于DataGrid使用的实例。

So get rid of 所以摆脱

<Grid.DataContext>
    <VM:Client/>
</Grid.DataContext>

and change your Content binding to a DataContext one 并将您的Content绑定更改为DataContext

<V:Client DataContext="{Binding currentClient}" .../>

and it should work fine. 它应该可以正常工作。

I thought for sure I had some rant on SO about why you should NEVER hardcode the DataContext property of a UserControl, but the closest thing I can find right now is this . 我以为我肯定对为什么不应该对UserControl的DataContext属性进行硬编码感到有些恼火,但是我现在能找到的最接近的东西是this Hopefully it can help you out :) 希望它可以帮助您:)

Oh boy that's a lot of code and I'm not sure I understand everything you're trying to do. 哦,男孩,这是很多代码,我不确定我是否了解您要尝试执行的所有操作。 Here are a few important notes when binding with WPF. 与WPF绑定时,有一些重要说明。

  1. Make sure the properties have public { get; 确保属性具有公共属性{get; set; 组; } }
  2. If the value is going to be changing, make sure you add Mode=TwoWay, UpdateSourceTrigger=PropertyChanged to your bindings. 如果该值将要更改,请确保将Mode = TwoWay,UpdateSourceTrigger = PropertyChanged添加到绑定中。 This keeps your view and view model in sync and tells each other to update. 这样可以使您的视图和视图模型保持同步,并告诉彼此进行更新。
  3. Use ObservableCollections and BindingLists for collections. 将ObservableCollections和BindingLists用于集合。 Lists don't seem to work very well. 列表似乎效果不佳。
  4. Make sure the DataContext matches the properties you are trying to bind to. 确保DataContext与您要绑定到的属性匹配。 If your DataContext doesn't have those properties, they aren't going to update. 如果您的DataContext没有这些属性,则它们将不会更新。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM