wpf将实例化对象绑定到datacontext

时间:2016-05-02 10:06:31

标签: wpf data-binding

编辑:问题不够明确。实际上有两个。

Q1:

我有一个UserControl“CustomView”,它是使用模板动态创建的:

<Window.Resources>
    <DataTemplate DataType="{x:Type my:CustomViewModel}">
        <my:CustomView/>
    </DataTemplate>
</Window.Resources>

<ItemsControl ItemsSource="{Binding Path=CustomList}"/>

其中CustomList是ObservableCollection类型的属性&lt;'CustomViewModel&gt;属于MainWindowViewModel,它是 Window 的DataContext。

在CustomView的Xaml代码中,有一些属性绑定到CustomViewModel的属性。一切正常。但是当我尝试在CustomView的代码中执行此操作时:

public CustomView()
{
    InitializeComponents();
    if (this.DataContext == null) Console.WriteLine ("DataContext is null");
    else Console.WriteLine(this.DataContext.GetType().ToString());
}

它是用Console编写的:'DataContext为null',即使绑定在CustomView和CustomViewModel之间工作也是如此。你知道它为什么有效吗?

Q2:

现在,假设CustomView内部有另一个UserControl(IndexPicker)。 IndexPicker也有一个关联的ViewModel(IndexPickerViewModel)负责数据访问。我需要将此IndexPickerViewModel的一个属性(“Index”)绑定到之前的CustomViewModel属性“Id”。我想在StaticResources中实例化它并将它绑定到CustomViewModel(根据我之前的问题我认为是dataContext):

<UserControl x:Class="MyView.CustomView"
...
<UserControl.Resources>
    <DataTemplate DataType="{x:Type myPicker:IndexPickerViewModel}">
        <myPicker:IndexPicker/>  
    </DataTemplate>
    <myPicker:IndexPickerViewModel x:Key="pickerViewModel" Index="{Binding Path=Id}/>
</Window.Resources/>

<ContentControl Content={StaticResource pickerViewModel}/>

我尝试过:我试图使“IndexPickerViewModel”继承自“DependencyObject”并使“Index”成为DependencyProperty。但是出现以下错误消息:

"System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Id; DataItem=null; target element is 'IndexPickerViewModel' (HashCode=59604175); target property is 'Index' (type 'Nullable`1')

我相信这是因为我上面提到的问题。但有可能做那样的事情吗?如果是的话,我错过了什么?而且:这是一个愚蠢的想法吗? 提前感谢您的帮助。

2 个答案:

答案 0 :(得分:0)

  

现在,假设CustomView内部有另一个UserControl(IndexPicker)。 IndexPicker也有一个关联的ViewModel(IndexPickerViewModel)负责数据访问。我需要将此IndexPickerViewModel的一个属性(“Index”)绑定到之前的CustomViewModel属性“Id”。我想在StaticResources中实例化它并将它绑定到CustomViewModel(根据我之前的问题我认为是dataContext)

如果IndexPicker没有明确设置的datacontext,那么IndexPicker will inherit the datacontext来自它的父元素。

但是如果IndexPicker已经有一个datacontext,那么你将不得不使用相对源绑定和祖先搜索:

Index="{Binding Id, RelaticeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, FallbackValue={x:Null}}"

当然,你可能已经感觉到这很混乱。追求UIElement或Control的标准属性是非常安全的(并且很常见),但是当你开始追踪自定义属性时,你就会在子控件和它的父控件之间引入依赖关系(当子控件不应该知道任何关于它的父级),你也必然会在某个阶段开始获取绑定错误(因此使用了回退值)。

答案 1 :(得分:0)

我似乎过早地问过,因为我自己找到了答案。

回答问题1

如果您有一个从DataTemplate动态创建的UserControl,它与另一个对象(属于ViewModel或资源)相关联,则此对象被定义为UserControl的DataContext。但是,你无法在UserControl的构造函数中访问它,你必须等到&#34; Loaded&#34;事件被提出:

public CustomUserControl()
{
    InitializeComponent();
    Console.WriteLine(this.DataContext.ToString());
    // This doesn't work : DataContext is null
}

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    Console.WriteLine(this.DataContext.ToString());
    // or
    Console.WriteLine((sender as UserControl).DataContext.ToString());
    // this is Ok.
}

回答问题2

这是为了获取一个UserControl,其ViewModel在父UserControl.Resources中实例化:

你不能这样做。

相反,您在其父ViewModel中实例化其ViewModel。完整的例子:

<强> MainWindow.xaml:

<Window x:Class="MainWindow"
    ...
    xmlns:local="clr-namespace:my_project_namespace"
    xmlns:cust="clr-namespace:CustomUserControl;assembly=CustomUserControl"
    ...>

    <Window.Resources>
        <DataTemplate DataType="{x:Type cust:CustomControlViewModel}">
            <cust:CustomControlView>
        </DataTemplate>
        <!-- Here are listed all the types inheriting from CustomControlViewModel and CustomControlView.-->
        <!-- CustomControlViewModel and CustomControlView are used as "abstract" classes-->
    </Window.Resources>

    <Window.DataContext>
        <local:MainWindowViewModel>
    </Window.DataContext>

    <Grid>
        <ItemsControl ItemsSource="{Binding Path=CustomVMList}"/>
    </Grid>
</Window>

<强> MainWindowViewModel.cs:

namespace my_project_namespace
{
    public class MainWindowViewModel
    {
        public ObservableCollection<CustomControlViewModel> CustomVMList { get; set; }
        public MainWindowViewModel()
        {
            CustomVMList = new ObservableCollection<CustomControlViewModel>();
            // Fill in the list...
        }
    }
}

<强> CustomControlView.xaml

<UserControl x:class="CustomUserControl.CustomControlView"
    ...
    xmlns:my="clr-namespace:IndexPicker;assembly=IndexPicker"
    ...>

    <UserControl.Resources>
        <DataTemplate DataType="{x:Type my:IndexPickerViewModel}">
            <my:IndexPickerView/>
        </DataTemplate>
    </UserControl.Resources>

    <StackPanel Orientation="Horizontal">
        <Label Content="{Binding Name}/>
        <ContentControl Content="{Binding Path=MyIndexPicker}"/>
    </Grid>
</UserControl>

这就是它的有趣之处:

<强> CustomControlViewModel.cs:

namespace CustomUserControl
{
    public class CustomControlViewModel : INotifyPropertyChanged
    {
        public IndexPickerViewModel MyIndexPicker{ get; set; }
        public string Name { get ; set; }
        public int Id
        {
            get
            {
                return MyIndexPicker.Index;
            }
            set
            {
                if (value != MyIndexPicker.Index)
                {
                    MyIndexPicker.Index = value;
                    NotifyPropertyChanged("Id");
                }
            }
        }

        public CustomControlViewModel(string _name)
        {
            Name = _name;
            MyIndexPicker = new IndexPickerViewModel();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
        }
    }
}

<强> IndexPickerView.xaml:

<UserControl x:Class="IndexPicker.IndexPickerView"
    ...
    ...>
    <Grid>
        <Combobox ItemsSource="{Binding Path=MyTable}"
            DisplayMemberPath="ColumnXYZ"
            SelectedItem={Binding Path=SelectedRow}/>
    </Grid>
</UserControl>

最后

<强> IndexPickerViewModel.cs:

namespace IndexPicker
{
    public class IndexPickerViewModel : INotifyPropertyChanged
    {
        private DataAccess data;
        public DataView MyTable { get; set; }

        private DataRowView selectedRow;
        public DataRowView SelectedRow
        {
            get { return selectedRow; }
            set
            {
                selectedRow = value;
                NotifyPropertyChanged("SelectedRow");
            }
        }

        public int? Index
        {
            get
            {
                if (SelectedRow != null) return (int?)selectedRow.Row["Column_Id"];
                else return null;
            }

            set
            {
                SelectedRow = MyTable[MyTable.Find((int)value)];
                NotifyPropertyChanged("Index");
            }
        }

        public IndexPickerViewModel()
        {
            data = new DataAccess();
            MyTable = data.GetTableView("tableName");
            MyTable.Sort = "Column_Id";
        }

        // And don't forget INotifyPropertyChanged implementation
    }
}

此配置与几个不同的 UserControls 一起使用,继承自 CustomControlView ,而 ViewModel 继承自 CustomControlViewModel 。它们是动态创建的,并在 CustomVMList 中列出。这里包含IndexPicker的CustomControlViewModel已经是一个专业化。

具体用法:CRUD数据库表的通用对话框,可根据每个表列动态创建UserControl。此处显示的专业化用于包含外键的列的情况。 我希望它清楚。

上面列出的代码可能包含错误。批评和评论是受欢迎的。

相关问题