0
votes

I have an ObservableCollection of ViewModels in my main ViewModel. The binding seems to work fine since I can switch views. However, raising the ViewModelBase OnPropertyChanged method (which work for other stuff) in an element of the ObservableCollection result in a null PropertyChanged value in ViewModelBase.

Here's my main code snippets:

In my main ViewModel Constructor:

    public EditorViewModel()
    {
        base.DisplayName = Strings.EditorName;

        _availableEditors = new ObservableCollection<ViewModelBase>();

        AvailableEditors.Add(new GBARomViewModel(646, 384));
        AvailableEditors.Add(new MonsterViewModel(800, 500));

        CurrentEditor = _availableEditors[0];
    }

At GBA ROM loading, ViewModel and Model properties are updated:

    void RequestOpenRom()
    {
        OpenFileDialog dlg = new OpenFileDialog();
        dlg.DefaultExt = ".gba";
        dlg.Filter = "GBA ROM (.gba)|*.gba|All files (*.*)|*.*";
        dlg.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

        Nullable<bool> result = dlg.ShowDialog();

        if (result == true)
        {
            if(CurrentEditor is GBARomViewModel)
            {
                (CurrentEditor as GBARomViewModel).ReadRom(dlg.FileName);
            }               
        }
    }

In my main View: Variation of TabControl (to have view switching and view states preservation).

    <controls:TabControlEx ItemsSource="{Binding AvailableEditors}"
        SelectedItem="{Binding CurrentEditor}"
        Style="{StaticResource BlankTabControlTemplate}"
        MinWidth="{Binding CurrentEditorWidth}"
        MinHeight="{Binding CurrentEditorHeight}"
        MaxWidth="{Binding CurrentEditorWidth}"
        MaxHeight="{Binding CurrentEditorHeight}"
        Width="{Binding CurrentEditorWidth}"
        Height="{Binding CurrentEditorHeight}"
        HorizontalAlignment="Left"
        VerticalAlignment="Top">
        <controls:TabControlEx.Resources>
            <DataTemplate DataType="{x:Type vm:GBARomViewModel}">
                <vw:GBARomView />
            </DataTemplate>
            <DataTemplate DataType="{x:Type vm:MonsterViewModel}">
                <vw:MonsterView />
            </DataTemplate>
        </controls:TabControlEx.Resources>
    </controls:TabControlEx>

In GBARomViewModel (child ViewModel, element of AvailableEditors)

    public String CRC32
    {
        get
        {
            return _rom.CRC32;
        }
        set
        {
            if (value.Equals(_rom.CRC32))
            {
                return;
            }

            _rom.CRC32 = value;
            OnPropertyChanged("CRC32");
        }
    }

Property binding in child View
Now this is a UserControl so I'll put its code as well after. Other properties at startup work such as LabelWidth and the LabelValue. Giving a default value to TextBoxValue in XAML also work.

    <StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10, 0, 0, 10" Width="300">
        <dlb:DefaultLabelBox LabelWidth="82" TextBoxWidth="100" HorizontalAlignment="Left" LabelValue="{x:Static p:Strings.RomTitle}" TextBoxValue="{Binding Title}" />
        <dlb:DefaultLabelBox LabelWidth="82" TextBoxWidth="100" HorizontalAlignment="Left" LabelValue="{x:Static p:Strings.RomGameCode}" TextBoxValue="{Binding GameCode}" />
        <dlb:DefaultLabelBox LabelWidth="82" TextBoxWidth="100" HorizontalAlignment="Left" LabelValue="{x:Static p:Strings.RomRomSize}" TextBoxValue="{Binding RomSize}"  />
        <dlb:DefaultLabelBox LabelWidth="82" TextBoxWidth="100" HorizontalAlignment="Left" LabelValue="{x:Static p:Strings.RomCRC32}" TextBoxValue="{Binding CRC32}"  />
        <dlb:DefaultLabelBox LabelWidth="82" TextBoxWidth="200" HorizontalAlignment="Left" LabelValue="{x:Static p:Strings.RomMD5Checksum}" TextBoxValue="{Binding MD5Checksum}"/>
    </StackPanel>

DefaultLabelBox.cs

<UserControl x:Name="uc">
    <StackPanel>
        <TextBlock Text="{Binding Path=LabelValue, ElementName=uc}"
           Width="{Binding Path=LabelWidth, ElementName=uc}"/>
        <Label Content="{Binding Path=TextBoxValue, Mode=OneWay, ElementName=uc}"
         Width="{Binding Path=TextBoxWidth, ElementName=uc}"/>
    </StackPanel>
</UserControl>

DefaultLabelBox.xaml.cs

    public string TextBoxValue
    {
        get {
            return (string)GetValue(TextBoxValueProperty);
        }
        set {
            SetValue(TextBoxValueProperty, value);
        }
    }

    public static readonly DependencyProperty TextBoxValueProperty =
        DependencyProperty.Register("TextBoxValue", typeof(string), typeof(DefaultLabelBox), new PropertyMetadata(default(string)));

Control Template

<Style TargetType="dlb:DefaultLabelBox">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="dlb:DefaultLabelBox">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding LabelValue, RelativeSource={RelativeSource TemplatedParent}}"
                               MinWidth="20"
                               Width="{Binding LabelWidth, RelativeSource={RelativeSource TemplatedParent}}"
                               VerticalAlignment="Center"
                               FontFamily="Mangal"
                               Height="20"
                               FontSize="13"/>
                    <Label Content="{Binding TextBoxValue, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
                           BorderBrush="{StaticResource DefaultLabelBoxBorderBrush}"
                           BorderThickness="1"
                           Padding="1,1,1,1"
                           Background="{StaticResource DefaultLabelBoxBackgroundBrush}"
                           Foreground="{StaticResource DefaultLabelBoxForeground}"
                               MinWidth="60"
                               Height="20"
                               VerticalAlignment="Center"
                               FontFamily="Mangal"
                               Width="{Binding TextBoxWidth, RelativeSource={RelativeSource TemplatedParent}}" 
                               FontSize="13"/>
                </StackPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

I tried a few things but being new to MVVM I don't know if I have a DataContext issue of a binding one. Any help would be appreciated.

Edit: I made changes to some code to illustrate the working solution for me as well as adding the ControlTemplate I had forgot. I'm not sure if Mode=OneWay is mandatory in UserControl and ControlTemplate but it's working now so I'm leaving it as it is.

1

1 Answers

1
votes

In order to make a binding like

<dlb:DefaultLabelBox ... TextBoxValue="{Binding CRC32, Mode=TwoWay}" />

work, the DefaultLabelBox needs to inherit its DataContext from its parent control (this is btw. the reason why a UserControl should never explicitly set its DataContext).

However, the "internal" bindings in the UserControl's XAML then need an explicitly specified Source or RelativeSource or ElementName.

So they should (for example) look like this:

<UserControl ... x:Name="uc">
    <StackPanel>
        <TextBlock
             Text="{Binding Path=LabelValue, ElementName=uc}"
             Width="{Binding Path=LabelWidth, ElementName=uc}"/>
        <TextBox
            Text="{Binding Path=TextBoxValue, Mode=TwoWay, ElementName=uc}"
            Width="{Binding Path=TextBoxWidth, ElementName=uc}"/>
    </StackPanel>
</UserControl>