简体   繁体   English

在视图模型和视图之间使用 MVVM 的 Wpf 数据上下文绑定

[英]Wpf datacontext binding using MVVM between viewmodel and view

I just started learning MVVM and here is what seems to be basic question but I spent whole day trying to figure it out.我刚开始学习 MVVM,这似乎是一个基本问题,但我花了一整天的时间试图弄明白。

I have a solution that contains 3 projects one for Model, one for ViewModel and one for View.我有一个包含 3 个项目的解决方案,一个用于模型,一个用于 ViewModel,一个用于 View。 The Model contains a class that has 2 properties Text and CheckStatus.该模型包含一个具有 2 个属性 Text 和 CheckStatus 的类。

The ViewModel has a list called listOfItems that has three items, each item has these 2 properties from the Model. ViewModel 有一个名为 listOfItems 的列表,其中包含三个项目,每个项目都具有模型中的这两个属性。

The View has a listView inside it there is a CheckBox. View 里面有一个 listView,里面有一个 CheckBox。 What is the proper way to bind the CheckBox content to the property Text?将 CheckBox 内容绑定到属性 Text 的正确方法是什么?

Here is the model这是模型

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;
   }
}

Here is the view model这是视图模型

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();
    }
   }
}

and here is the View XAML这是 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>

Here is the View behind code, I know I shouldn't have something here but where should that part go?这是代码背后的视图,我知道我不应该在这里放东西,但是那部分应该放在哪里?

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());
    }
  }
 }

First of all.首先。 Set dependencies of projects.设置项目的依赖关系。 ViewModel must have access Model. ViewModel 必须有权访问模型。 (View and Model projects do not have to reference to other projects.) If i were you i would make a StartUp Project to transfer the control to ViewModel. (视图和模型项目不必引用其他项目。)如果我是你,我会创建一个启动项目以将控制权转移到 ViewModel。 This "StartUp" project should be WPF, all of others should be "class library" but don't forget to add the required references to projects (For example the system.xaml for your view project to create usercontrols.)这个“启动”项目应该是 WPF,所有其他项目应该是“类库”,但不要忘记添加对项目的必需引用(例如,用于创建用户控件的视图项目的 system.xaml。)

Projects dependencies: - StartUp --> ViewModel;项目依赖项:- StartUp --> ViewModel; (- ViewModel --> View; or avoid this with DI) - ViewModel --> Model; (- ViewModel --> View;或使用 DI 避免这种情况)- ViewModel --> Model; (I should make another project for interfaces just this is just my perversions.) (我应该为界面做另一个项目,这只是我的变态。)

StartUp Project : Now in your startup (WPF) project should contains in (app.xaml.cs):启动项目:现在在您的启动 (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());
} 

The only one thing (Window) in your startup wpf project (to display your UserControls).启动 wpf 项目中唯一的一件事(窗口)(显示您的用户控件)。

MainWindow.xaml content: 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>

(and xaml.cs) (和 xaml.cs)

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

And Thats all your StartUp WPF project.这就是您所有的 StartUp WPF 项目。 In this way we gave the control to your ViewModel project.通过这种方式,我们将控制权交给了您的 ViewModel 项目。

(Okay, its just an extra, but i should make a "ViewService" to handle my UserControls) (好吧,这只是一个额外的,但我应该制作一个“ViewService”来处理我的用户控件)

Interface to find all of View and match the View with ViewModel.用于查找所有 View 并将 View 与 ViewModel 匹配的接口。

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

I created a singleton to store and match my views with my viewmodels.我创建了一个单例来存储我的视图并将其与我的视图模型匹配。 (You can skip this part.) I defined this in Model project. (你可以跳过这部分。)我在模型项目中定义了这个。

 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;
        }

    }

And now you have got a "service" to show your UserControls in the mainwindow from your ViewModel :现在你有一个“服务”来在你的ViewModel的主窗口中显示你的用户控件:

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;
    }

If you don't want to use this "ViewService".如果你不想使用这个“ViewService”。 Just create an UserControl instance, match DataContext of View with your ViewModel and give this view to Control property.只需创建一个 UserControl 实例,将 View 的 DataContext 与您的 ViewModel 相匹配,并将此视图提供给 Control 属性。 Here is your ViewModel with list (still in ViewMoldel project.)这是带有列表的 ViewModel(仍在 ViewModel 项目中。)

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 RelayCommand 文章

View: (Keep here all of your usercontrols.)查看:(在这里保留所有用户控件。)

In your UserControl (xaml):在您的用户控件 (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>

And with interface here is the xaml.cs code (for UserControls):这里的接口是 xaml.cs 代码(用于 UserControls):

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

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

And the last one is the Model project with your models:最后一个是带有模型的模型项目:

 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;
   }
}

Excuse me for english grammar mistakes, i hope you understood my post.请原谅我的英语语法错误,我希望你能理解我的帖子。

Update: Use the DI techn.更新:使用 DI 技术。 to avoid the reference to view from viewmodel.避免从视图模型中引用视图。 DI service will inject the correct object with constructor injection. DI 服务将通过构造函数注入注入正确的对象。

This is all quite ridiculous.这一切都非常荒谬。

Here is a much easier way which involves no external libraries, no additional housekeeping classes and interfaces, almost no magic, and is very flexible because you can have viewmodels that contain other viewmodels, and you get to instantiate each one of them, so you can pass constructor parameters to them:这是一种更简单的方法,它不涉及外部库,没有额外的管理类和接口,几乎没有魔法,而且非常灵活,因为您可以拥有包含其他视图模型的视图模型,并且可以实例化它们中的每一个,因此您可以将构造函数参数传递给它们:

For the viewmodel of the main window:对于主窗口的视图模型:

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();
    }
}

For all other viewmodels:对于所有其他视图模型:

This is in MainViewModel.cs:这是在 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 );
    }
}

This in MainView.xaml:这在 MainView.xaml 中:

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

As you can see, a viewmodel can simply be a member (child) of another viewmodel;如您所见,一个视图模型可以只是另一个视图模型的成员(子); in this case SomeViewModel and OtherViewModel are children of MainViewModel.在这种情况下,SomeViewModel 和 OtherViewModel 是 MainViewModel 的子级。 Then, in the XAML file of MainView, you can just instantiate each of the child views and specify their DataContext by Binding to the corresponding child viewmodels.然后,在 MainView 的 XAML 文件中,您可以实例化每个子视图并通过Binding到相应的子视图模型来指定它们的DataContext

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

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

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

相关问题 MVVM:当DataContext不为空时,通过使用DataTemplate将viewmodel(DataContext)绑定到视图 - MVVM: When DataContext is not null by using DataTemplate to bind viewmodel(DataContext) to the view WPF MVVM模式,ViewModel DataContext问题 - WPF MVVM Pattern, ViewModel DataContext question WPF MVVM-在显示视图之前未在ViewModel中应用绑定(异步/等待) - Wpf MVVM - Binding not applied in ViewModel before View is showing (Async / await) WPF MVVM - 将多个视图/视图模型绑定到同一个基本视图模型 - WPF MVVM - Binding multiple View/ViewModels to same base ViewModel 使用MVVM在XAML / WPF中的导航上设置视图的DataContext - Set the DataContext of a View on a Navigation in XAML/WPF using MVVM MVVM模式:命令绑定和ViewModel执行之间的中间视图 - MVVM pattern: an intermediate View between Command binding and ViewModel execute 从 MVVM 中的 viewModel 绑定到视图 - Binding to view from viewModel in MVVM 带有子 UserControl/ViewModel 数据绑定问题的 WPF MVVM 父视图/ViewModel - WPF MVVM Parent View/ViewModel with child UserControl/ViewModel Data Binding Issue 在C#中使用Model-View-ViewModel(MVVM)的WPF FolderBrowserDialog - WPF FolderBrowserDialog using Model-View-ViewModel (MVVM) in c# wpf中使用datacontext进行数据绑定 - data binding in wpf using datacontext
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM