在viewmodel和view之间使用MVVM的Wpf datacontext绑定

时间:2017-04-26 18:09:22

标签: c# wpf mvvm data-binding datacontext

我刚刚开始学习MVVM,这似乎是一个基本的问题,但我花了一整天时间试图解决它。

我有一个解决方案,其中包含3个项目,一个用于Model,一个用于ViewModel,另一个用于View。 Model包含一个具有2个Text和CheckStatus属性的类。

ViewModel有一个名为listOfItems的列表,它有三个项目,每个项目都包含Model中的这两个属性。

View里面有一个listView,里面有一个CheckBox。将CheckBox内容绑定到属性Text的正确方法是什么?

这是模型

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace TheModel
{
public class CheckBoxListModel : INotifyPropertyChanged
{
    private string text;
    public string Text
    {
        get { return text; }
        set
        {
            text = value;
            RaiseChanged("Text");
        }
    }

    private bool checkStatus;
    public bool CheckStatus
    {
        get { return checkStatus; }
        set
        {
            checkStatus = value;
            RaiseChanged("CheckStatus");
        }
    }

    private void RaiseChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
   }
}

这是视图模型

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using TheModel;

namespace TheViewModel
{
public class TheViewModel
{
    public List<CheckBoxListModel> ListOfItems { get; set; }

    public TheViewModelClass()
    {
        ListOfItems = new List<CheckBoxListModel>
        {
        new CheckBoxListModel
        {
            CheckStatus = false,
            Text = "Item 1",
        },
        new CheckBoxListModel
        {
            CheckStatus = false,
            Text = "Item 2",
        },
        new CheckBoxListModel
        {
            CheckStatus = false,
            Text = "Item 3",
        }
    };
    }

    public static implicit operator List<object>(TheViewModelClass v)
    {
        throw new NotImplementedException();
    }
   }
}

这是View XAML

 <UserControl
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:ctrl="clr-namespace:TheView.Managers" xmlns:TheViewModel="clr-
 namespace:TheViewModel;assembly=TheViewModel" 
 x:Class="TheView.Styles.ListViewDatabaseStyle">

<UserControl.DataContext>
    <TheViewModel:TheViewModelClass/>
</UserControl.DataContext>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="100"/>
    </Grid.RowDefinitions>
    <Button Content="Continue" Style="{StaticResource ButtonStyle}" 
          Margin="1104,27,40,40"/>
    <ListView x:Name="listView1" SelectionMode="Multiple" 
              Style="{StaticResource ListViewStyle}" Margin="10,55,10,10"
              ctrl:ListViewLayoutManager.Enabled="true" ItemsSource="
          {Binding TheViewModelClass}" >
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Competency Items" 
                  ctrl:ProportionalColumn.Width="1100"/>
            </GridView>
        </ListView.View>
        <ListView.ItemContainerStyle >
            <Style TargetType="{x:Type ListViewItem}">
                <Setter Property="IsSelected" Value="{Binding 
                             CheckedStatus}"/>
                <Setter Property="HorizontalContentAlignment" 
                              Value="Stretch"/>
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.ItemTemplate>
            <DataTemplate>
                <CheckBox  
                     Click="CheckBox_Click"
                     Content="{Binding Path=TheViewModelClass.Text}"
                     IsChecked="{Binding 
                     Path=TheViewModelClass.CheckedStatus}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>
</UserControl>

这是View背后的代码,我知道我不应该在这里找到一些东西,但该部分应该去哪里?

using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System;
using System.Text;
using TheViewModel;

namespace TheView.Styles
{
public partial class ListViewDatabaseStyle : UserControl
{
    public ListViewDatabaseStyle()
    {
        InitializeComponent();
    }

    public List<string> selectedNames = new List<string>();
    private void CheckBox_Click(object sender, RoutedEventArgs e)
    {
        var ChkBox = sender as CheckBox;
        var item = ChkBox.Content;
        bool isChecked = ChkBox.IsChecked.HasValue ? ChkBox.IsChecked.Value 
         : false;
        if (isChecked)
            selectedNames.Add(item.ToString());
        else
            selectedNames.Remove(item.ToString());
    }
  }
 }

3 个答案:

答案 0 :(得分:3)

首先。设置项目的依赖关系。 ViewModel必须具有访问模型。 (View和Model项目不必引用其他项目。)如果我是你,我会创建一个StartUp Project来将控件转移到ViewModel。 这个“StartUp”项目应该是WPF,所有其他项目应该是“类库”,但不要忘记添加项目所需的引用(例如,为您的视图项目创建用户控件的system.xaml。)

项目依赖项:   - StartUp - &gt;视图模型;  ( - ViewModel - &gt;查看;或使用DI避免这种情况)   - ViewModel - &gt;模型; (我应该为接口制作另一个项目,这只是我的变态。)

启动项目: 现在你的启动(WPF)项目应该包含在(app.xaml.cs)中:

protected override void OnStartup(StartupEventArgs e)
{
    // delete the startupuri tag from your app.xaml
    base.OnStartup(e);
    //this MainViewModel from your ViewModel project
    MainWindow = new MainWindow(new MainViewModel());
} 

启动wpf项目中唯一的一件事(Window)(显示你的UserControls)。

MainWindow.xaml内容:

<Window x:Class="StartUp.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" WindowState="Maximized" WindowStyle="None" AllowsTransparency="True">
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Control}"/>
</Window>

(和xaml.cs)

  public partial class MainWindow : Window
    {
        public MainWindow(INotifyPropertyChanged ViewModel)
        {
            InitializeComponent();
            this.DataContext = ViewModel;
            this.Show();
        }
    }

这就是你所有的StartUp WPF项目。 通过这种方式,我们将控件提供给您的ViewModel项目。

(好吧,它只是一个额外的,但我应该制作一个“ViewService”来处理我的UserControls)

查找所有View的界面,并将View与ViewModel匹配。

public interface IControlView
{
    INotifyPropertyChanged ViewModel { get; set; }
}

我创建了一个单例来存储我的视图和我的视图模型。 (您可以跳过此部分。)我在模型项目中定义了这个。

 public class ViewService<T> where T : IControlView
    {
        private readonly List<WeakReference> cache;

        public delegate void ShowDelegate(T ResultView);
        public event ShowDelegate Show;
        public void ShowControl<Z>(INotifyPropertyChanged ViewModel)
        {
            if (Show != null)
                Show(GetView<Z>(ViewModel));
        }

        #region Singleton

        private static ViewService<T> instance;
        public static ViewService<T> GetContainer
        {
            get
            {
                if (instance == null)
                {
                    instance = new ViewService<T>();
                }
                return instance;
            }
        }

        private ViewService()
        {
            cache = new List<WeakReference>();
            var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()).Where(r => typeof(T).IsAssignableFrom(r) && !r.IsInterface && !r.IsAbstract && !r.IsEnum);

            foreach (Type type in types)
            {
                cache.Add(new WeakReference((T)Activator.CreateInstance(type)));
            }
        }

        #endregion

        private T GetView<Z>(INotifyPropertyChanged ViewModel)
        {
            T target = default(T);
            foreach (var wRef in cache)
            {
                if (wRef.IsAlive && wRef.Target.GetType().IsEquivalentTo(typeof(Z)))
                {
                    target = (T)wRef.Target;
                    break;
                }
            }

            if(target==null)
                target = (T)Activator.CreateInstance(typeof(Z));

            if(ViewModel != null)
                target.ViewModel = ViewModel;

            return target;
        }

    }

现在你有一个“服务”来显示你的主窗口中的UserControl 的视图模型

public class MainViewModel : INotifyPropertyChanged
    {

        private IControlView _control;
        public IControlView Control
        {
            get
            {
                return _control;
            }
            set
            {
                _control = value;
                OnPropertyChanged();
            }
        }

        public MainViewModel()
        {   //Subscribe for the ViewService event:   
            ViewService<IControlView>.GetContainer.Show += ShowControl;
            // in this way, here is how to set a user control to the window.
            ViewService<IControlView>.GetContainer.ShowControl<ListViewDatabaseStyle>(new TheViewModel(yourDependencyItems));
           //you can call this anywhere in your viewmodel project. For example inside a command too.
        }

        public void ShowControl(IControlView ControlView)
        {
            Control = ControlView;
        }

        //implement INotifyPropertyChanged...
        protected void OnPropertyChanged([CallerMemberName] string name = "propertyName")
        {
           PropertyChangedEventHandler handler = PropertyChanged;
           if (handler != null)
           {
               handler(this, new PropertyChangedEventArgs(name));
           }
        }

           public event PropertyChangedEventHandler PropertyChanged;
    }

如果您不想使用此“ViewService”。只需创建一个UserControl实例,将View的DataContext与ViewModel匹配,并将此视图提供给Control属性。 这是带有列表的ViewModel(仍在ViewMoldel项目中。)

public class TheViewModel
    {
        private readonly ObservableCollection<ISelectable> listOfItems;
        public ObservableCollection<ISelectable> ListOfItems 
        {
            get { return listOfItems; }
        }

        public ICommand SaveCheckedItemsText{
            get{ return new RelayCommand(CollectNamesOfSelectedElements);}
        }

        public IEnumerable<ISelectable> GetSelectedElements
        {
            get { return listOfItems.Where(item=>item.CheckStatus); }
        }

        public TheViewModel(IList<ISelectable> dependencyItems)
        {
            listOfItems= new ObservableCollection<ISelectable>(dependencyItems);
        }

        //here is your list...
        private List<string> selectedNames

        //use this...
        private void CollectNamesOfSelectedElements()
        {
           selectedNames = new List<string>();
           foreach(ISelectable item in GetSelectedElements)
           {
             //you should override the ToString in your model if you want to do this...
             selectedNames.Add(item.ToString());
           }
        }

    }

RelayCommand article

查看:(请保留所有用户控件。)

在您的UserControl(xaml)中:

<UserControl x:Class="View.ListViewDataStyle"
             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" namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
             mc:Ignorable="d">
<Button Command={Binding SaveCheckedItemsText}/>
<!-- Another content -->
    <ListView ItemsSource="{Binding ListOfItems}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</UserControl>

这里的接口是xaml.cs代码(对于UserControls):

public partial class ListViewDatabaseStyle : UserControl, IControlView
    {
        public ListViewDatabaseStyle ()
        {
            InitializeComponent();
        }

        public INotifyPropertyChanged ViewModel
        {
            get
            {
                return (INotifyPropertyChanged)DataContext;
            }
            set
            {
                DataContext = value;
            }
        }
    }

最后一个是模型的模型项目:

 public interface ISelectable
    {
        bool CheckStatus { get; set; }
    }

public class CheckBoxListModel : INotifyPropertyChanged, ISelectable
{
    private string text;
    public string Text
    {
        get { return text; }
        set
        {
            text = value;
            RaiseChanged("Text");
        }
    }

    private bool checkStatus;
    public bool CheckStatus
    {
        get { return checkStatus; }
        set
        {
            checkStatus = value;
            RaiseChanged("CheckStatus");
        }
    }

    private void RaiseChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
   }
}

对不起英语语法错误,我希望你理解我的帖子。

<强>更新 使用DI技术。避免从viewmodel引用视图。 DI服务将使用构造函数注入注入正确的对象。

答案 1 :(得分:1)

<UserControl.DataContext>
    <TheViewModel:TheViewModelClass/>
</UserControl.DataContext>

<ListView ItemsSource="{Binding ListOfItems}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

答案 2 :(得分:0)

这太荒谬了。

这是一种简单得多的方法,它不涉及外部库,不需要其他管家类和接口,几乎没有魔术,并且非常灵活,因为您可以拥有包含其他视图模型的视图模型,并且可以实例化每个视图模型,因此您可以将构造函数参数传递给它们:

对于主窗口的视图模型:

using Wpf = System.Windows;

public partial class TestApp : Wpf.Application
{
    protected override void OnStartup( Wpf.StartupEventArgs e )
    {
        base.OnStartup( e );
        MainWindow = new MainView();
        MainWindow.DataContext = new MainViewModel( e.Args );
        MainWindow.Show();
    }
}

对于所有其他视图模型:

这在MainViewModel.cs中:

using Collections = System.Collections.Generic;

public class MainViewModel
{
    public SomeViewModel SomeViewModel { get; }
    public OtherViewModel OtherViewModel { get; }
    public Collections.IReadOnlyList<string> Arguments { get; }
    
    public MainViewModel( Collections.IReadOnlyList<string> arguments )
    {
        Arguments = arguments;
        SomeViewModel = new SomeViewModel( this );
        OtherViewModel = new OtherViewModel( this );
    }
}

在MainView.xaml中

[...]
xmlns:local="clr-namespace:the-namespace-of-my-wpf-stuff"
[...]
    <local:SomeView DataContext="{Binding SomeViewModel}" />
    <local:OtherView DataContext="{Binding OtherViewModel}" />
[...]

如您所见,一个视图模型可以简单地成为另一个视图模型的成员(子)。在这种情况下,SomeViewModel和OtherViewModel是MainViewModel的子级。然后,在MainView的XAML文件中,您只需实例化每个子视图并将DataContext通过Binding指定给相应的子视图模型。