【问题标题】:wpf binding instantiated object to datacontextwpf 将实例化对象绑定到数据上下文
【发布时间】:2016-05-02 10:06:31
【问题描述】:

编辑:问题不够清楚。实际上有两个。

第一季度:

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

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

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

其中 CustomList 是属于 MainWindowViewModel 的 ObservableCollection 类型的属性,它是 Window 的 DataContext。

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

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

它是在控制台中编写的:“DataContext 为空”,即使绑定在 CustomView 和 CustomViewModel 之间有效。你知道它为什么起作用吗?

第二季度:

现在,假设 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')

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

【问题讨论】:

  • 你为什么要绑定?只使用StaticResource单独设置内容。
  • @Emperor Aiman - 你是对的,这是一个错误。不过,问题在于资源对象和 dataContext 之间的绑定。

标签: wpf data-binding


【解决方案1】:

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

如果 IndexPicker 没有明确设置的数据上下文,则 IndexPicker will inherit the datacontext 来自它的父元素。

但是,如果IndexPicker 确实已经有一个数据上下文,那么您将不得不将相对源绑定与祖先搜索一起使用:

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

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

【讨论】:

  • 感谢您的回答。我刚刚编辑了这个问题,因为它不够清楚......
【解决方案2】:

看来我问得太早了,因为我自己找到了答案。

问题1的答案

当您有一个从 DataTemplate 动态创建的 UserControl 时,它与另一个对象(属于 ViewModel 或资源)相关联,该对象被定义为 UserControl 的 DataContext。但是,您无法在 UserControl 的构造函数中访问它,您必须等到引发“Loaded”事件:

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的答案

这就是你如何获得一个 ViewModel 在父 UserControl.Resources 中实例化的 UserControl:

你不这样做。

相反,您在其父 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 一起使用,这些 UserControls 继承自 CustomControlView,它们的 ViewModel 继承自 CustomControlViewModel。它们是动态创建的,并列在 CustomVMList 中。这里包含 IndexPicker 的 CustomControlViewModel 已经是一个特化了。

具体使用:CRUD数据库Tables的Generic Dialog,可以根据每个Table Columns动态创建UserControl。此处显示的特化用于包含外键的列的情况。 我希望它清楚。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 1970-01-01
    • 2018-03-29
    • 2015-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多