0
votes

I have made a User Control, FontSelector, that groups together a ComboBox for FontFamily Selection and three ToggleButtons for Bold, Italics, Underline options. I am having an issue with the ComboBox's SelectedItem property affecting all instances of that User Control within the same Window. For example, changing the ComboBox selection on one, will automatically change the other. For Clarity. I don't want this behavior. I am very surprised that a User Control is implicitly affecting another User Control.

XAML

<Grid x:Name="Grid" Background="White" DataContext="{Binding RelativeSource={RelativeSource AncestorType=local:FontSelector}}">        
    <ComboBox x:Name="comboBox" Width="135"
              SelectedItem="{Binding Path=SelectedFontFamily}" Style="{StaticResource FontChooserComboBoxStyle}"
                              ItemsSource="{Binding Source={StaticResource SystemFontFamilies}}"/>
</Grid>

Code Behind

The CLR Property that the ComboBox's SelectedItem is Bound to. Code shown here is in the User Control Code Behind File, not a ViewModel.

        private FontFamily _SelectedFontFamily;
    public FontFamily SelectedFontFamily
    {
        get
        {
            return _SelectedFontFamily;
        }
        set
        {
            if (_SelectedFontFamily != value)
            {
                _SelectedFontFamily = value;

                // Modify External Dependency Property Value.
                if (value != SelectedFont.FontFamily)
                {
                    SelectedFont = new Typeface(value, GetStyle(), GetWeight(), FontStretches.Normal);
                }

                // Notify.
                RaisePropertyChanged(nameof(SelectedFontFamily));
            }
        }
    }

The Dependency Property that updates it's value based on the Value of the ComboBox's SelectedItem Property. It effectively packages the FontFamily value into a Typeface Object.

public Typeface SelectedFont
    {
        get { return (Typeface)GetValue(SelectedFontProperty); }
        set { SetValue(SelectedFontProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SelectedFont.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedFontProperty =
        DependencyProperty.Register("SelectedFont", typeof(Typeface), typeof(FontSelector),
            new FrameworkPropertyMetadata(new Typeface("Arial"), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                new PropertyChangedCallback(OnSelectedFontPropertyChanged)));

    private static void OnSelectedFontPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var instance = d as FontSelector;
        var newFont = e.NewValue as Typeface;

        if (newFont != null)
        {
            instance.SelectedFontFamily = newFont.FontFamily;

        }
    }

EDIT

I think I may have figured out what is going on. I can replicate it by Binding the ItemsSource to the Following Collection View Source.

<CollectionViewSource  x:Key="SystemFontFamilies" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
        <CollectionViewSource.SortDescriptions>
             <scm:SortDescription PropertyName="Source"/>
        </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>

You can then replicate the behavior by placing 2 ComboBoxes and Binding both of them to the CollectionViewSource. They will now, seemingly implicitly track each others SelectedItem. Even without Any Data Binding outside of ItemsSource. It would seem that the CollectionViewSource is somehow playing a part in what the SelectedItem is.

2
Is the SelectedFontFamily property in you ViewModel? May be when one UC updates it, the property updates other UC's. - Nikhil Agrawal
Forgot to mention. I am not using a View Model for this particular Control. The Binding Source's are implemented in the Code Behind File. I chose not to use a VM for this one because it seemed to be a lot of extra work to get the Data generated by the VM back to the Dependency property in the actual User Control Code behind. - Charlie Hall
I think the question was if you bind the SelectedFontFamily property in the window. Because then the change in one element would update the form's viewmodel which in turn would update the other controls. - Geoffrey
I'm afraid I'm not following. I am not using a dedicated ViewModel class for this control. The SelectedFontFamily property exists in the Code Behind of the FontSelector User Control. There, I would think that with 2 instances of FontSelector, there should also be two separate Instances of the SelectedFontFamily property.. The same case as if you had two instances of the same ViewModel class assigned as the DataContext of separate Controls, they should have entirely independent property values... At least that's what I think should happen. - Charlie Hall

2 Answers

1
votes

I'd make it a bit different. I'll introduce this solution using only a String, not FontFamily or FontWeight, since I have no VS here right now. (In order to have it working, please change the list of FontFamilies to a list of strings to bind them.)

Your selector UserControl:
- your xaml is ok (but you won't need the x:Name)
- the CodeBehind of the UserControl (later: UC) should change, we will solve it with binding. You should have a DependencyProperty, lets' call it SelectedFontFamily, which will represent the selected string from the ComboBox:

public string SelectedFontFamily
{
    get { return (string)GetValue(SelectedFontFamilyProperty); }
    set { SetValue(SelectedFontFamilyProperty, value); }
}

public static readonly DependencyProperty SelectedFontFamilyProperty = DependencyProperty.Register("SelectedFontFamily", typeof(string), typeof(YourUC), new PropertyMetadata(string.Empty));

The Window, which contains the UC:
- You should include the namespace of the UC's folder in the opening tag of the window, eg:

<Window 
...
xmlns:view="clr-namespace:YourProjectName.Views.UserControls">

- the window's DataContext should have a property with public set option (feel free to implement INotifyPropertyChange on it):

public string FontFamily {get; set;}

- in the Window's xaml you would use the UC this way:

<view:YourUC SelectedFontFamily="{Binding FontFamily, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

It's a two-way binding. You'll find the selected string as the value of the FontFamily property every time you change the SelectedItem.

Edit: you will need View Model class for the Window which is using the UserControl. Create it, make it implement the INotifyPropertyChanged interface, and set it as DataContext for your consumer window. WPF is not like WF, you can find more about it if you Google up "WPF MVVM" or something like that.

0
votes

Found the problem. I was binding to a CollectionViewSource defined in Application Resources. Until now I was unaware that Binding to a CollectionViewSource will also affect the SelectedItem. The SelectedItem Data gets stored as part of the CollectionViewSource. Setting the IsSynchronizedWithCurrentItem property to False on the ComboBox solved the issue.

Here is an existing answer I have now Found.

Thanks