Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing additional arguments to user control inside the data template

This is the xaml code what i am using

  <GridView
        Grid.Row="0"
        x:Name="RootGrid"
        SelectionMode="None"
        IsItemClickEnabled="True"
        ItemsSource="{Binding RootListSource}">

        <GridView.ItemTemplate>
            <DataTemplate>
                <UserControl:TreeInfoControl/>
            </DataTemplate>
        </GridView.ItemTemplate>

    </GridView>

In this my user control, it contain another GridView that holds a different IEnumerable collection. What i am trying to achieve is i need to pass this collection through code. I tried this by adding a dependency property to the treecontrol but it is not working. So i am looking for a solution that enable passing the collection through xaml (somehow through the user control). I know it is possible to add that collection to my existing collection and bind that one. But for now i can't use that method.

like image 935
StezPet Avatar asked Jan 02 '14 16:01

StezPet


1 Answers

Here's how you do it.

Start with your App.xaml so we can reuse the demo template

<Application.Resources>
    <DataTemplate x:Key="MyContentControl">
        <Grid Height="100" Width="100" Background="Maroon">
            <TextBlock Text="{Binding FallbackValue=0}" Foreground="White" FontSize="40" VerticalAlignment="Center" HorizontalAlignment="Center" />
        </Grid>
    </DataTemplate>
</Application.Resources>

Then we can define your user control

<d:UserControl.DataContext>
    <local:MyControlViewModel Number="-1" Letter="~K" />
</d:UserControl.DataContext>

<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left">
    <ContentControl Content="{Binding Number}" 
                    ContentTemplate="{StaticResource MyContentControl}" />
    <ListView ItemsSource="{Binding Letters}" IsHitTestVisible="False"
              ItemTemplate="{StaticResource MyContentControl}"
              SelectedItem="{Binding Letter, Mode=TwoWay}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <ItemsStackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ListView>
</StackPanel>

And then we can define your MainPage.xaml

<Page.DataContext>
    <local:MainPageViewModel Letter="C" />
</Page.DataContext>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="140" />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <ListView x:Name="MyList" ItemsSource="{Binding Letters}" 
              ItemTemplate="{StaticResource MyContentControl}"
              SelectedItem="{Binding Letter, Mode=TwoWay}" />
    <ListView Grid.Column="1" ItemsSource="{Binding Numbers}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <StackPanel.Resources>
                        <local:MyControlViewModel 
                            x:Key="MyDataContext" Number="{Binding}" 
                            Letters="{Binding ItemsSource, ElementName=MyList}" 
                            Letter="{Binding SelectedItem, ElementName=MyList}" />
                    </StackPanel.Resources>
                    <local:MyControl DataContext="{StaticResource MyDataContext}" />
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

Nothing special yet, right? Well, not so fast. We're creating the viewmodel for the user control , setting the properties of the view model from the surrounding scope, then passing it in to the DataContext of the user control explicitly. Cool, huh? Simple enough, if you think about it. Want to set those properties inside the tag? Sure you do. But you can't. The order of operation would be all wrong. You'll just have to trust me.

Now, there's ZERO code behind for your user control. But the view model looks like this:

public class MyControlViewModel : BindableBase
{
    public int Number
    {
        get { return (int)GetValue(NumberProperty); }
        set
        {
            SetValue(NumberProperty, value);
            base.RaisePropertyChanged();
        }
    }
    public static readonly DependencyProperty NumberProperty =
        DependencyProperty.Register("Number", typeof(int), typeof(MyControlViewModel),
        new PropertyMetadata(0, (s, e) => { }));

    public string Letter
    {
        get { return (string)GetValue(LetterProperty); }
        set
        {
            SetValue(LetterProperty, value);
            base.RaisePropertyChanged();
        }
    }
    public static readonly DependencyProperty LetterProperty =
        DependencyProperty.Register("Letter", typeof(string), typeof(MyControlViewModel),
        new PropertyMetadata("Z", (s, e) => { }));

    public ObservableCollection<string> Letters
    {
        get { return (ObservableCollection<string>)GetValue(LettersProperty); }
        set
        {
            SetValue(LettersProperty, value);
            base.RaisePropertyChanged();
        }
    }
    public static readonly DependencyProperty LettersProperty =
        DependencyProperty.Register("Letters", typeof(ObservableCollection<string>),
        typeof(MyControlViewModel),
        new PropertyMetadata(new ObservableCollection<string>(new[] { "~W", "~X", "~Y", "~Z" }), (s, e) => { }));
}

All the properties are dependency properties. I hope you noticed. I didn't just do that because I like to type. Though I do like to type. Fact is, I did that because in order to have internal binding you must use a dependency property - and a dependency property that raises property changed! That last part isn't trivial. But does it have to be in a view model? No. But I like it that way.

You might reference this: http://blog.jerrynixon.com/2013/07/solved-two-way-binding-inside-user.html

There's also no code behind for your MainPage. But the view model looks like this:

public class MainPageViewModel : BindableBase
{
    public MainPageViewModel()
    {
        this._Letters = new ObservableCollection<string>(new[] { "A", "B", "C", "D" });
        this._Numbers = new ObservableCollection<int>(new[] { 1, 2, 3, 4 });
    }

    public string Letter
    {
        get { return (string)GetValue(LetterProperty); }
        set
        {
            SetValue(LetterProperty, value);
            base.RaisePropertyChanged();
        }
    }
    public static readonly DependencyProperty LetterProperty =
        DependencyProperty.Register("Letter", typeof(string), typeof(MyControlViewModel),
        new PropertyMetadata("Z", (s, e) => { }));

    ObservableCollection<string> _Letters = new ObservableCollection<string>();
    public ObservableCollection<string> Letters { get { return _Letters; } }

    ObservableCollection<int> _Numbers = new ObservableCollection<int>();
    public ObservableCollection<int> Numbers { get { return _Numbers; } }
}

The bindable base is standard, here's the code for it:

public abstract class BindableBase : DependencyObject, System.ComponentModel.INotifyPropertyChanged
{

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    protected void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
    {
        if (!object.Equals(storage, value))
        {
            storage = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
    protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
}

When it's all done, you should get exactly what you want. Something like this:

enter image description here

Not to over-simplify things. But, it's that easy.

Look, getting your head wrapped around XAML is not always easy when you start to nest contexts. I don't blame you for not getting it on first run. But I hope this helps you get started. Keep pushing

Best of luck!

like image 159
Jerry Nixon Avatar answered Feb 12 '23 01:02

Jerry Nixon