views:

485

answers:

5

Windows Forms allows you to develop Components, non-visual elements that can have a designer. Built-in components include the BackgroundWorker, Timer, and a lot of ADO .NET objects. It's a nice way to provide easy configuration of a complicated object, and it it enables designer-assisted data binding.

I've been looking at WPF, and it doesn't seem like there's any concept of components. Am I right about this? Is there some method of creating components (or something like a component) that I've missed?

I've accepted Bob's answer because after a lot of research I feel like fancy Adorners are probably the only way to do this.

+1  A: 

So far, the only approach I see that makes sense is to make an instance of the class a static resource and configure it from XAML. This works, but it'd be nice if there were something like the WinForms designer component tray that these could live in.

OwenP
A: 

You would need to derive from the FrameworkElement as that is the base class used for those components that need to be placed into the hierarchy but are not visual elements. A visual element would derive from UIElement instead.

Phil Wright
You are incorrect about this. FrameworkElement derives from UIElement which derives from Visual. That means that FrameworkElement is a visual element.You can actually program right against the visual layer by deriving from Visual or by (more easily) using DrawingVisual.
cplotts
+5  A: 

Just from my own observations, it seems like Microsoft is trying to move away from having components and similar things in the GUI. I think WPF tries to limit most of what's in the XAML to strictly GUI things. Data binding I guess would be the only exception. I know I try to keep most everything else in the code-behind or in separate classes or assemblies.

Probably not exactly the answer you wanted, but it's my $0.02.

Bob
+1  A: 

You can put whatever you like inside a resource dictionary, including classes that have no relation what so ever to Wpf.

The following XAML adds the string "Hello" directly into a window (the actual string, not a control that shows the string), you can use the same method to place anything - including classes you write yourself into a XAML file.

<Window  x:Class="MyApp.Window1"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    >
<Window.Resources>
    <sys:String x:Key="MyString">Hello</sys:String>
</Window.Resources>
</Window>
Nir
A: 

I have the same question. The advantage of a component-like mechanism is that the designer can add it in Blend, configure it in the designer with the Properties editor, and use data binding. What do you think of the solution below? It works.

public class TimerComponent : FrameworkElement
{
    public Timer Timer { get; protected set; }

    public TimerComponent()
    {
        if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
        {
            Visibility = Visibility.Collapsed;
            Timer = new Timer(OnTimerTick, null, Timeout.Infinite, Timeout.Infinite);
        }
    }

    void OnTimerTick(object ignore)
    {
        Dispatcher.BeginInvoke(new Action(RaiseTickEvent));
    }

    #region DueTime Dependency Property

    public int DueTime
    {
        get { return (int)GetValue(DueTimeProperty); }
        set { SetValue(DueTimeProperty, value); }
    }

    public static readonly DependencyProperty DueTimeProperty =
        DependencyProperty.Register("DueTime", typeof(int), typeof(TimerComponent), new UIPropertyMetadata(new PropertyChangedCallback(OnDueTimeChanged)));

    static void OnDueTimeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var target = obj as TimerComponent;
        if (target.Timer != null)
        {
            var newDueTime = (int)e.NewValue;
            target.Timer.Change(newDueTime, target.Period);
        }
    }

    #endregion

    #region Period Dependency Property

    public int Period
    {
        get { return (int)GetValue(PeriodProperty); }
        set { SetValue(PeriodProperty, value); }
    }

    public static readonly DependencyProperty PeriodProperty =
        DependencyProperty.Register("Period", typeof(int), typeof(TimerComponent), new UIPropertyMetadata(new PropertyChangedCallback(OnPeriodChanged)));

    static void OnPeriodChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var target = obj as TimerComponent;
        if (target.Timer != null)
        {
            var newPeriod = (int)e.NewValue;
            target.Timer.Change(target.DueTime, newPeriod);
        }
    }

    #endregion

    #region Tick Routed Event

    public static readonly RoutedEvent TickEvent = EventManager.RegisterRoutedEvent(
        "Tick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TimerComponent));

    public event RoutedEventHandler Tick
    {
        add { AddHandler(TickEvent, value); }
        remove { RemoveHandler(TickEvent, value); }
    }

    private void RaiseTickEvent()
    {
        RoutedEventArgs newEventArgs = new RoutedEventArgs(TimerComponent.TickEvent);
        RaiseEvent(newEventArgs);
    }

    #endregion
}

And is used as follows.

<StackPanel>
    <lib:TimerComponent Period="{Binding ElementName=textBox1, Path=Text}" Tick="OnTimerTick" />
    <TextBox x:Name="textBox1" Text="1000" />
    <Label x:Name="label1" />
</StackPanel>
Wally