4
\$\begingroup\$

Exposing Inherited Members

I have a WPF Custom Control library (ContentToggleButton) and an app (spec) to test it. The Custom Controls, which derive from Presentation Framework elements, expose inherited properties as well as new properties to the consumer.

This means that the consuming app depends on the Framework Elements as well as the Custom Controls as shown by the four dependencies from the top and bottom of MainWindow in the following diagram.

enter image description here

Reimplementing Inherited Members

The references were due to the IsChecked dependency property of the ToggleButton base class of the ContentToggle object and the Click event of the ButtonBase base class of the ButtonContent object. The former was bound in the XAML to a method on the MainWindow class in the View, code behind.

<b:ContentToggle Name="Toggle" Height="30"
                 Options="{Binding ToggleButtonView.Options, 
                            RelativeSource={StaticResource View}}"
                 IsChecked="{Binding ToggleButtonView.InitialState,
                            Mode=OneWay,
                            RelativeSource={StaticResource View}}" />
<b:ContentButton x:Name="Plain" Height="30" 
                 Options="{Binding PlainButtonView.Options, 
                            RelativeSource={StaticResource View}}" />

The RelativeSource resource is defined in Resource Dictionary in App.xaml.

<!--Get a reference to the window to establish View Context-->
<RelativeSource x:Key="View" Mode="FindAncestor" 
            AncestorType="{x:Type Window}" />

The second dependency was due to subscribing to the Click event inherited from ButtonBase in the MainWindow class.

Plain.Click += (s, e) => {
    Toggle.IsEnabled = !Toggle.IsEnabled;
};

By adding a custom DP called InitialState on ContentToggle and binding to that instead then using it to initialise IsChecked in the Loaded event, one of the dependencies was eliminated.

public static readonly DependencyProperty 
    InitialStateProperty = DependencyProperty.Register(
    "InitialState", typeof(bool?), typeof(ContentToggle), 
    new PropertyMetadata(false));

public bool? InitialState
{
    get { return (bool?) GetValue(InitialStateProperty); }
    set { SetValue(InitialStateProperty, value); }
}

The second dependency was eliminated by reimplementing the Click event in the custom property and routing the original event through it.

public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent(
    "Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ContentToggle));

public new event RoutedEventHandler Click
{
    add { AddHandler(ClickEvent, value); }
    remove { RemoveHandler(ClickEvent, value); }
}

void RaiseClickEvent (object o, RoutedEventArgs e)
{
    RoutedEventArgs newEventArgs = new RoutedEventArgs(ContentToggle.ClickEvent);
    RaiseEvent(newEventArgs);
}
public ContentToggle()
{
    base.Click += RaiseClickEvent;

}

As a result, the consuming app is unaware of the primitive components with dependencies only on the CC Library.

enter image description here

Is this a good idea or a bad one? Is it worth the extra code?

\$\endgroup\$
  • \$\begingroup\$ What problem are you trying to solve? I don't follow. \$\endgroup\$ – Nikita B Dec 1 '16 at 7:55
  • \$\begingroup\$ @NikitaB, should I be adding the extra code to wrap inherited members in order to simplify dependencies? Is there a practical benefit to having the second structure compared to the first? What is the best practice about this? \$\endgroup\$ – Cool Blue Dec 1 '16 at 8:03
2
\$\begingroup\$

Well, this looks like a really bad idea.

  1. To begin with... what's the point? I mean what is so bad about having a dependency on standard .Net assembly? It is already installed on the machine as part of .Net installation, so this dependency does not affect the size of your app. Am I missing something?
  2. This design works under fairly naive assumption, that the app will not change in any way as the time goes on. And it might be the case for your particular app, but it is not the case in general. Imagine I am using your library, and I want to subscribe to ContentToggle.Loaded event. What should I do? Should I call you at 3AM and ask you to also override this event with new keyword? Will you do the same thing for every single event (or property) found on FrameworkElement? Will you then test every single interaction, to make sure you did not break standard behavior? If you are not willing to take this all the way - don't do it at all.
  3. I don't even want to begin imagining all the potential pitfalls. What will happen if someone sets e.Handled = true; for your new Click event? Will your base event keep going? What will happen, if I have a style for regular button that uses <EventSetter Event="Click" .../> and I want to override this setter for your button's style? How is it going to work, will I have two event handlers as a result or just one? Etc, etc.
\$\endgroup\$
  • \$\begingroup\$ OK, good: that's what I figured. This is just a study exercise, not meant to be a production app, so don't get too carried away with the details. My question is because I read a lot about how important decoupling is (MVVM and IoC for example) so I'm trying to understand the implications of that. I was wondering how important it is, in the big picture, to limit dependencies. \$\endgroup\$ – Cool Blue Dec 2 '16 at 4:54
  • \$\begingroup\$ Regarding the Loaded event: it's is already overriden so no need to call me. Regarding <EventSetter Event="Click"...: I did some experiments and it seems that it is filtered by the TargetType. The Button.Click event will not hit any EventSetter in a style targeting b:ContentControl for example. It will only hit EventSetters in styles targeting the custom type. \$\endgroup\$ – Cool Blue Dec 2 '16 at 5:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.