如何在代码后面将View中的自定义属性绑定到ViewModel?

时间:2015-10-18 14:06:48

标签: c# wpf mvvm binding

我创建了一个页面MainPage和一个UserControl Pager。两者都有自己的ViewModel。在Pager中,有三个依赖项属性RowsColumnsSource。我想将这些属性从Pager的View传递给Pager的ViewModel。我在后面的View代码上尝试了这个。但它不起作用...... set中的PagerViewModel属性永远不会被调用。求你帮帮我......

这是详细机制:

MainPageViewModel

↓传递带有绑定的值

MainPage

↓使用MainPagerViewModel

中的值设置属性

Pager(代码背后)

↓将属性绑定到PagerViewModel< ---这部分是问题!!!

PagerViewModel

↓传递带有绑定的值

Pager(XAML)

这是源

[MainPageViewModel.cs]

using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using Client.Model;

namespace Client.ViewModel
{
    public class MainPageViewModel : ViewModelBase
    {
        ...
        public ObservableCollection<IPagableEntry> PagerTableCategoriesItems { get { return TableCategoryRepository.Instance.TableCategories; } }

        public int PagerTableCategoriesRows { get { return 1; } }

        public int PagerTableCategoriesColumns { get { return 3; } }
        ...
    }
}

[MainPage.xaml中]

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:view="clr-namespace:Client.View"
      xmlns:viewModel="clr-namespace:Client.ViewModel"
      xmlns:resStr="clr-namespace:Client.CommonResources.String"
      x:Class="Client.View.MainPage"
      Style="{StaticResource common}">
    <Page.DataContext>
        <viewModel:MainPageViewModel />
    </Page.DataContext>
    ...

    <view:Pager x:Name="pagerTableCategories"
                Source="{Binding Path=PagerTableCategoriesItems}"
                Rows="{Binding Path=PagerTableCategoriesRows}"
                Columns="{Binding Path=PagerTableCategoriesColumns}">
    </view:Pager>
    ...
</Page>

[Pager.xaml.cs]

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using Client.Model;
using Client.ViewModel;

namespace Client.View
{
    public partial class Pager
    {

        public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(ObservableCollection<IPagableEntry>), typeof(Pager), new PropertyMetadata(null, OnSourceChanged));
        public static readonly DependencyProperty RowsProperty = DependencyProperty.Register("Rows", typeof(int), typeof(Pager), new PropertyMetadata(1, OnRowsChanged));
        public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(Pager), new PropertyMetadata(1, OnColumnsChanged));
        public static readonly DependencyProperty SelectedEntryProperty = DependencyProperty.Register("SelectedEntry", typeof(object), typeof(Pager), new PropertyMetadata(null, OnSelectedEntryChanged));

        public int Rows
        {
            get { return (int)GetValue(RowsProperty); }
            set { SetValue(RowsProperty, value); }
        }

        public int Columns
        {
            get { return (int)GetValue(ColumnsProperty); }
            set { SetValue(ColumnsProperty, value); }
        }

        public object SelectedEntry
        {
            get { return GetValue(SelectedEntryProperty); }
            set { SetValue(SelectedEntryProperty, value); }

        }

        public ObservableCollection<IPagableEntry> Source
        {
            get { return (ObservableCollection<IPagableEntry>)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        public Pager()
        {
            InitializeComponent();

            // I want to bind the three custom properties(Rows, Columns, Source) to PagerViewModel's Rows, Columns, Collection
            Binding bindingRows = new Binding("Rows");
            bindingRows.Mode = BindingMode.TwoWay;
            bindingRows.Source = gridPager.DataContext;
            gridPager.SetBinding(RowsProperty, bindingRows);

            Binding bindingColumns = new Binding("Columns");
            bindingColumns.Mode = BindingMode.TwoWay;
            bindingColumns.Source = gridPager.DataContext;
            gridPager.SetBinding(ColumnsProperty, bindingColumns);

            Binding bindingSource = new Binding("Collection");
            bindingSource.Mode = BindingMode.TwoWay;
            bindingSource.Source = gridPager.DataContext;
            gridPager.SetBinding(SourceProperty, bindingSource);
        }

        private void ListBoxEntriesOnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            SelectedEntry = (sender as ListBox).SelectedItem;
        }

        private static void OnSelectedEntryChanged(DependencyObject pager, DependencyPropertyChangedEventArgs e)
        {
            (pager as Pager).SelectedEntry = e.NewValue;
        }

        private static void OnSourceChanged(DependencyObject pager, DependencyPropertyChangedEventArgs e)
        {
            (pager as Pager).Source = (ObservableCollection<IPagableEntry>)e.NewValue;
        }

        private static void OnRowsChanged(DependencyObject pager, DependencyPropertyChangedEventArgs e)
        {
            (pager as Pager).Rows = (int)e.NewValue;
        }

        private static void OnColumnsChanged(DependencyObject pager, DependencyPropertyChangedEventArgs e)
        {
            (pager as Pager).Columns = (int)e.NewValue;
        }

    }
}

[Pager.xaml]

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:view="clr-namespace:Client.View"
             xmlns:viewModel="clr-namespace:Client.ViewModel"
             xmlns:resStr="clr-namespace:Client.CommonResources.String"
             x:Class="Client.View.Pager">
    <Grid x:Name="gridPager">
        <Grid.DataContext>
            <viewModel:PagerViewModel />
        </Grid.DataContext>

        ...

        <ListBox x:Name="listBoxEntries"
                 ItemsSource="{Binding Path=Collection}"
                 BorderThickness="0"
                 Margin="0"
                 Style="{StaticResource common}"
                 HorizontalContentAlignment="Stretch"
                 VerticalContentAlignment="Stretch"
                 ItemTemplate="{StaticResource templateTableCategory}"
                 SelectedItem="{Binding Path=SelectedEntry, Mode=TwoWay}"
                 SelectionChanged="ListBoxEntriesOnSelectionChanged">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Rows="{Binding Path=Rows}"
                                 Columns="{Binding Path=Columns}"
                                 IsItemsHost="True"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
        </ListBox>

        ...

    </Grid>
</UserControl>

[PagerViewModel.cs]

using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Data;
using System.Windows.Media;
using Client.Model;

namespace Client.ViewModel
{
    public class PagerViewModel : ViewModelBase
    {

        ...

        ListCollectionView _listCollectionView;
        ObservableCollection<IPagableEntry> _collection;
        int _rows;
        int _columns;

        public int Rows
        {
            get { return _rows; }
            set
            {
                _rows = value;
                OnPropertyChanged();
            }
        }

        public int Columns
        {
            get { return _columns; }
            set
            {
                _columns = value;
                OnPropertyChanged();
            }
        }

        public ListCollectionView ListCollectionView
        {
            get { return _listCollectionView; }
            set
            {
                _listCollectionView = value;
                OnPropertyChanged();
            }
        }

        public ObservableCollection<IPagableEntry> Collection
        {
            get
            {
                return _collection;
            }

            set
            {
                _collection = value;
                OnPropertyChanged();
            }
        }

        ...

    }
}

4 个答案:

答案 0 :(得分:0)

您发布的代码有两个明显的问题:

  1. 您的OnXXXChanged()处理程序不执行任何操作。他们正在响应他们反过来试图设置的属性的变化。即他们只是重申他们被通知的属性设置,而不是在某个不同的对象中设置属性值。
  2. 您正在尝试在不存在的属性上设置绑定。即代码隐藏SetBinding()次调用的目标是gridPager对象,它只是Grid。它没有要设置的RowsColumnsSource属性。
  3. 假设我们暂时使用绑定来实现这一点,那么你有第三个问题:

    1. 您将尝试将两个不同的源属性绑定到同一目标属性。例如。 Pager.Rows已成为 MainPage.xaml 中建立的绑定的目标,其中PagerTableCategoriesRows为源。它也不能是来自任何其他对象的绑定目标(最重要的是,来自它自身的循环绑定,如果使用Pager.RowsProperty依赖属性,这是唯一有意义的源,因为代码正在尝试做)。
    2. 我对即使这样做的智慧也不完全清楚。似乎Pager元素可以直接绑定到Pager属性本身,从而继承原始视图模型中的值,而不是维护第二个完全独立但有意相同的视图模型。

      但是假设有一个很好的理由我根本就不明白,你打算在这里使用两种不同的视图模型,并希望让它们保持同步,在我看来你应该能够让它工作通过更改OnXXXChanged()处理程序,以便他们直接设置视图模型值。 E.g:

      public partial class Pager
      {
      
          public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(ObservableCollection<IPagableEntry>), typeof(Pager), new PropertyMetadata(null, OnSourceChanged));
          public static readonly DependencyProperty RowsProperty = DependencyProperty.Register("Rows", typeof(int), typeof(Pager), new PropertyMetadata(1, OnRowsChanged));
          public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(Pager), new PropertyMetadata(1, OnColumnsChanged));
          public static readonly DependencyProperty SelectedEntryProperty = DependencyProperty.Register("SelectedEntry", typeof(object), typeof(Pager));
      
          public int Rows
          {
              get { return (int)GetValue(RowsProperty); }
              set { SetValue(RowsProperty, value); }
          }
      
          public int Columns
          {
              get { return (int)GetValue(ColumnsProperty); }
              set { SetValue(ColumnsProperty, value); }
          }
      
          public object SelectedEntry
          {
              get { return GetValue(SelectedEntryProperty); }
              set { SetValue(SelectedEntryProperty, value); }
      
          }
      
          public ObservableCollection<IPagableEntry> Source
          {
              get { return (ObservableCollection<IPagableEntry>)GetValue(SourceProperty); }
              set { SetValue(SourceProperty, value); }
          }
      
          public Pager()
          {
              InitializeComponent();
          }
      
          private void ListBoxEntriesOnSelectionChanged(object sender, SelectionChangedEventArgs e)
          {
              SelectedEntry = (sender as ListBox).SelectedItem;
          }
      
          private static void OnSourceChanged(DependencyObject pager, DependencyPropertyChangedEventArgs e)
          {
              ((PagerViewModel)(pager as Pager).gridPager.DataContext).Collection =
                  (ObservableCollection<IPagableEntry>)e.NewValue;
          }
      
          private static void OnRowsChanged(DependencyObject pager, DependencyPropertyChangedEventArgs e)
          {
              ((PagerViewModel)(pager as Pager).gridPager.DataContext).Rows =
                  (int)e.NewValue;
          }
      
          private static void OnColumnsChanged(DependencyObject pager, DependencyPropertyChangedEventArgs e)
          {
              ((PagerViewModel)(pager as Pager).gridPager.DataContext).Columns =
                  (int)e.NewValue;
          }
      }
      

      顺便说一句:我不鼓励你在上面使用as。主要原因是,如果出于某种原因,演员表失败,那么一个不太有用的NullReferenceException将是您的第一个可见症状,而不是InvalidCastException

      我建议仅在预期时使用as,有些时候,演员表会失败。当然,在这种情况下,您也始终检查null结果并正确处理。

      如果您希望演员表总是成功,那么请使用演员操作符。

答案 1 :(得分:0)

此解决方案假定:

  1. 父窗口/页面对UC一无所知。但UC知道它的父母。
  2. UC依赖于从其父级传播到它的DataContext。
  3. 此代码的作用:

    1. UC创建与MainViewModel的MainWinProp1属性的双向绑定。
    2. 在UC中进行的任何更改都可在MainViewModel中看到,反之亦然。
    3. 怎么做?

      通过自己的DataContext在UC中获取MainViewModel。由于UC DataContext自动从parentwin dctx获取其值。但是可能会发生这种情况,例如,您的UC存在于某些Grid of MainWin中,而此Grid正在使用其他一些视图模型。在这种情况下,您必须使用一些VisualTree遍历帮助器方法到达根窗口/页面以获取它的datacontext。

      https://www.dropbox.com/s/5ryc9ndxdu2m6a4/WpfApplication3.rar?dl=0

      如果您不希望您的UC依赖于您的父母,而是父母使用您的UC,那么从父母那里您可以随时轻松访问UC并按照您的意愿行事。

答案 2 :(得分:0)

您的主要困惑是由于Pager不是视图,而是UserControl

它们都可以从UserControl类继承,但不同之处在于:在MVVM中,View(或Subview aka DataTemplate)具有绑定到它的ViewModel(通过DataContext)或它的父级,但没有“代码背后”的“功能”。

另一方的UserControl永远不会有属于它的ViewModel(读取:逻辑未拆分为ViewModel),因为UserControl意味着可以跨多个应用程序重用,View是特定于ViewModel的,只能在您的应用程序中使用。

UserControl中,后面的代码完全有效(对于Dependency Properties,其他Application ViewModels可以绑定或登录后面的代码)。 UserControl将公开DP以进行外部数据绑定。在View中,这是绝对禁止并违反MVVM模式。

这是一个非常常见的陷阱开发人员遇到MVVM并尝试为控件创建ViewModel并发现自己陷入困境。

话虽如此,因为(imho,从您上面的示例中我看不到您的寻呼机中的任何应用特定功能),您的PagerUserControl,它不需要PagerViewModel 1}}并且它的代码应该移到Pager的Code Behind。

在旁注 从您的Pager的ViewModel类中可以明显看出,您的尝试是验证MVVM,因为它保留了对View的强引用(不仅是View类,还有 ANY WPF相关类!!!)。 / p>

using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
// MVVM Violation, it's part of WPF 
using System.Windows.Data;
// MVVM Violation, it's part of WPF (usually PresentationFramework.dll)
using System.Windows.Media;
using Client.Model;

namespace Client.ViewModel
{
    public class PagerViewModel : ViewModelBase
    {

        ...

        // MVVM Violation, it's a type from Assembly:  PresentationFramework (in PresentationFramework.dll)
        ListCollectionView _listCollectionView;
        ObservableCollection<IPagableEntry> _collection;
        int _rows;
        int _columns;

        public int Rows
        {
            get { return _rows; }
            set
            {
                _rows = value;
                OnPropertyChanged();
            }
        }

        public int Columns
        {
            get { return _columns; }
            set
            {
                _columns = value;
                OnPropertyChanged();
            }
        }

        public ListCollectionView ListCollectionView
        {
            get { return _listCollectionView; }
            set
            {
                _listCollectionView = value;
                OnPropertyChanged();
            }
        }

        public ObservableCollection<IPagableEntry> Collection
        {
            get
            {
                return _collection;
            }

            set
            {
                _collection = value;
                OnPropertyChanged();
            }
        }

        ...

    }
}

如果您创建单独的程序集,则可以更轻松地执行MVVM。

  1. MyApp.Desktop / MyApp.UniversalApp / MyApp.Web:允许引用PresentationFramework.dll和下面的所有其他程序集
  2. MyApp.ViewModels :此程序集仅允许包含ViewModel。仅引用模型,接口,域。引用Presentation.dll和MyApp.Data。* | MSSQL | Oracle | MySQL | SAP是严格禁止的。
  3. MyApp.Core / MyApp.Shared / MyApp.Domain :包含您的业务逻辑。参考模型和基础设施
  4. (可选) MyApp.Models :您的模型,没有别的。
  5. (可选) MyApp.Infrastructure / MyApp.Abstractions :包含您的服务接口(适用于存储库或服务)。没有参考或仅参考模型
  6. (可选)MyApp.Data。* | MSSQL | Oracle | MySQL | SAP:您的数据库特定实施以及不属于您的域的所有内容。仅参考基础架构和域
  7. 这非常有用,就好像你尝试在Model或ViewModel中使用Presentation.dll中的Type一样,它会失败,因为没有对程序集的引用,你立即知道:“哇!停在这里。我做错了什么,它违反了MVVM!

    粗体下划线 程序集必须能够在其他平台(Web,桌面,WinPhone,Silverlight)上运行和编译,因此不允许它们具有此引用查看特定的程序集。 Datalayer可能与平台不同(即WinPhone应用程序可能希望使用SQLite而不是MSSQL,Linux上的ASP.NET网站可能更喜欢MySQL到MSSQL等。)

答案 3 :(得分:0)

我可以理解,问题是在两个视图模型之间创建同步机制。我对彼得的理论完全不满意,但我建议您使用下一个同步解决方案。为了解决这个问题,我建议您使用模型级同步。只需将您需要的细节放入一个轻型模型类中,然后将这个小模型注入所需的视图模型中。这是方案: enter image description here

的问候,