-1

I've created a tab bar using a user control because I need the tab bar to be on multiple pages as it changes the current page. This means the tab bar has to show which page is currently shown by moving a dark rectangle behind the correct image. So I tried using a custom property called SelectionIndex to move the selection rectangle behind the correct image, 0 being the Home image and 1 being the Schedule image etc. The code below shows the tab bar being used on a page where the selected image should be the second one.

    <Grid>
        <local:TabBar SelectionIndex="1" Margin="0,0,1222,0"/>
    </Grid>

The code below has been generated by propdp and I changed the name of the property to SelectionIndex. I then use a Switch-Case to check what the value of the property is and change the margin of the rectangle image to move it.

public partial class TabBar : UserControl
{
    public int SelectionIndex
    {
        get { return (int)GetValue(SelectionIndexProperty); }
        set { SetValue(SelectionIndexProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SelectionIndex.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectionIndexProperty =
        DependencyProperty.Register("SelectionIndex", typeof(int), typeof(TabBar), new PropertyMetadata(0));


    public TabBar()
    {
        InitializeComponent();

        switch (SelectionIndex)
        {
            case 0:
                SelectionBox.Margin = new Thickness(0, 10, 0, 0);
                break;
            case 1:
                SelectionBox.Margin = new Thickness(0, 123, 0, 0);
                break;
        }
    }
}

However, I put a break point on switch (SelectionIndex) to check the value of SelectionIndex, it was 0 but should be 1. In an attempt to fix this myself, I changed the property type to a string from an int, this showed me that the SelectionIndex isn't "0", it's null.

I have Googled this issue but it seems everyone else is having trouble Binding a value to the property, I just want to be able to set the property to an int between 0-5 in the XAML.

2 Answers2

3

The problem is that you are doing this in the constructor and the value is set on the property after the control is created.

You need to add a property changed handler on the dependency property and invoke a method to handle the change :

public partial class TabBar : UserControl
{
    ....
    // Using a DependencyProperty as the backing store for SelectionIndex.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectionIndexProperty =
        DependencyProperty.Register("SelectionIndex", typeof(int), typeof(TabBar), new PropertyMetadata(0, propertyChangedCallback: (d,e)=> ((TabBar)d).OnSelectionIndexChanged()));


    public void OnSelectionIndexChanged()
    {
        switch (SelectionIndex)
        {
            // ....
        }
    }
}
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
2

It's not null, it's zero.

But your trouble, narrowly speaking, is that the attributes aren't assigned until after the constructor completes. More broadly though, you're not taking advantage of the way WPF lets you do things.

In WPF, this can be done without much C# code. Don't use margins for positioning, use a Grid. Position the "marker" border within the grid by binding its Grid.Column property to SelectedIndex. Simple as can be, and now you can resize the items in the grid and everything will work magically. If you want to put two things in column three, it's always better to say "put this and that both in column three" rather than "put this at 356 units offset" and somewhere else say "put this at uh... what was the offset for column three again?" Then you change the design next year and forget to update one of them.

<Grid>
    <Grid.ColumnDefinitions>
        <!-- Made up width values -->
        <ColumnDefinition Width="100" />
        <ColumnDefinition Width="100" />
        <ColumnDefinition Width="100" />
        <ColumnDefinition Width="100" />
    </Grid.ColumnDefinitions>
    <Border
        Background="DeepSkyBlue"
        Grid.Column="{Binding SelectionIndex, RelativeSource={RelativeSource AncestorType=UserControl}}"
        >
    </Border>
    <Label Grid.Column="0">Click Me</Label>
    <Label Grid.Column="1">No, Click Me</Label>
    <Label Grid.Column="2">Me Me Me!</Label>
    <Label Grid.Column="3">Wahhhh</Label>
</Grid>

However

If you wanted to stick with your original approach, you could move that switch statement to a Loaded event, but a "better" way to do it would be with a PropertyChanged handler on the dependency property. That way, whenever the property changes, your control will respond to the change appropriately. We'll make the default value -1, otherwise the property changed handler won't ever be called:

public int SelectionIndex
{
    get { return (int)GetValue(SelectionIndexProperty); }
    set { SetValue(SelectionIndexProperty, value); }
}

public static readonly DependencyProperty SelectionIndexProperty =
    DependencyProperty.Register(nameof(SelectionIndex), typeof(int), typeof(TabBar),
        new FrameworkPropertyMetadata(-1, SelectionIndex_PropertyChanged));

protected static void SelectionIndex_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    (d as TabBar).OnSelectionIndexChanged(e.OldValue);
}

private void OnSelectionIndexChanged(object oldValue)
{
    switch (SelectionIndex)
    {
        case 0:
            SelectionBox.Margin = new Thickness(0, 10, 0, 0);
            break;
        case 1:
            SelectionBox.Margin = new Thickness(0, 123, 0, 0);
            break;
    }
}