0

EDIT : Question was not clear enough. In fact there are two of them.

Q1 :

I have a UserControl "CustomView" that is dynamically created with a template:

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

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

Where CustomList is a Property of type ObservableCollection<'CustomViewModel> belonging to MainWindowViewModel, which is the Window's DataContext.

In CustomView's Xaml code, there are some Properties binded to CustomViewModel's Properties. Everything works properly. But when I try to do this in CustomView's code behind :

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

It is written in Console : 'DataContext is null', even if bindings are working betweeen CustomView and CustomViewModel. Do you know why it's working?

Q2 :

Now, imagine that CustomView has another UserControl (IndexPicker) inside of it. IndexPicker has an associated ViewModel too (IndexPickerViewModel) who's in charge with data access. I need to bind one property ("Index") of this IndexPickerViewModel to the previous CustomViewModel's property "Id". I want to instantiate it in StaticResources and bind it to the CustomViewModel (which I believe is the dataContext according to my previous question):

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

What I have tried : I tried to make "IndexPickerViewModel" inherit from "DependencyObject" and make "Index" a DependencyProperty. But the following error message shows up :

"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')

I believe this is because of what I asked just above. But is it possible to do something like that? If yes, what am I missing? And : Is this a stupid idea? Thank you in advance for any help.

heliar
  • 91
  • 9
  • Why are you binding ?Just set the Content using StaticResource alone. – wingerse May 02 '16 at 11:11
  • @Emperor Aiman - You are right, it was a mistake. Still, the problem is the binding between the resources object and the dataContext. – heliar May 02 '16 at 12:17

2 Answers2

0

Now, imagine that CustomView has another UserControl (IndexPicker) inside of it. IndexPicker has an associated ViewModel too (IndexPickerViewModel) who's in charge with data access. I need to bind one property ("Index") of this IndexPickerViewModel to the previous CustomViewModel's property "Id". I want to instantiate it in StaticResources and bind it to the CustomViewModel (which I believe is the dataContext according to my previous question)

If IndexPicker doesn't have an explicitly set datacontext then IndexPicker will inherit the datacontext from it's parent element.

However if IndexPicker does already have a datacontext then you will have to use relative source binding with an ancestor search:

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

Of course you can probably already sense that this is messy. Going after standard properties of a UIElement or Control is quite safe (and common), but when you start going after custom properties then you are introducing dependencies between the child control and its parent (when the child control shouldn't know much of anything about its parent), and you are also bound to start getting binding errors at some stage (hence the use of a fallback value).

Community
  • 1
  • 1
slugster
  • 49,403
  • 14
  • 95
  • 145
0

It seems that I've asked too early because I've found answers by myself.

Answer to Question1

When you have a UserControl that is dynamically created from a DataTemplate in which it is associated with another object (belonging to a ViewModel or to a Resource), this object is defined as the DataContext of the UserControl. However, you cannot reach it in the UserControl's constructor, you have to wait until the "Loaded" event is raised :

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

Answer to Question2

This is how you do to get a UserControl whose ViewModel is instantiated in a parent UserControl.Resources :

You don't do it.

Instead, you instantiate its ViewModel in its parent ViewModel. Full example :

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>

And this is where it's interesting :

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>

Finally

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

This configuration is used with several different UserControls inheriting from CustomControlView and their ViewModel inheriting from CustomControlViewModel. They are dynamically created and listed in CustomVMList. Here CustomControlViewModel containing an IndexPicker is already a specialization.

Concrete use: Generic Dialog for CRUD database Tables, which can dynamically create UserControls depending on each Table Columns. The specialization shown here is used in case of a column containing a foreign key. I hope its clear.

The code listed above may contain mistakes. Criticisms and remarks are welcome.

heliar
  • 91
  • 9