我创建了一个页面MainPage
和一个UserControl Pager
。两者都有自己的ViewModel。在Pager
中,有三个依赖项属性Rows
,Columns
,Source
。我想将这些属性从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();
}
}
...
}
}
答案 0 :(得分:0)
您发布的代码有两个明显的问题:
OnXXXChanged()
处理程序不执行任何操作。他们正在响应他们反过来试图设置的属性的变化。即他们只是重申他们被通知的属性设置,而不是在某个不同的对象中设置属性值。SetBinding()
次调用的目标是gridPager
对象,它只是Grid
。它没有要设置的Rows
,Columns
或Source
属性。假设我们暂时使用绑定来实现这一点,那么你有第三个问题:
Pager.Rows
已成为 MainPage.xaml 中建立的绑定的目标,其中PagerTableCategoriesRows
为源。它也不能是来自任何其他对象的绑定目标(最重要的是,来自它自身的循环绑定,如果使用Pager.RowsProperty
依赖属性,这是唯一有意义的源,因为代码正在尝试做)。我对即使这样做的智慧也不完全清楚。似乎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)
此解决方案假定:
此代码的作用:
怎么做?
通过自己的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,从您上面的示例中我看不到您的寻呼机中的任何应用特定功能),您的Pager
是UserControl
,它不需要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。
这非常有用,就好像你尝试在Model或ViewModel中使用Presentation.dll中的Type一样,它会失败,因为没有对程序集的引用,你立即知道:“哇!停在这里。我做错了什么,它违反了MVVM!“
粗体下划线 程序集必须能够在其他平台(Web,桌面,WinPhone,Silverlight)上运行和编译,因此不允许它们具有此引用查看特定的程序集。 Datalayer可能与平台不同(即WinPhone应用程序可能希望使用SQLite而不是MSSQL,Linux上的ASP.NET网站可能更喜欢MySQL到MSSQL等。)
答案 3 :(得分:0)