1

I am trying to create a very simple WPF User Control to represent a digital clock.

I have a couple of things I want client code to be able to change, e.g. foreground text colour, font etc., so I've made some public properties for them. Some of the code is shown below:

    public partial class DigitalClock : System.Windows.Controls.UserControl
    {
        public string Color { get; set; }

        private Timer timer;            
        private string DisplayString { get { return DateTime.Now.ToString("dd-MM-yy HH:mm:ss"); } }

        public DigitalClock()
        {
            InitializeComponent();                    
            this.timer = new Timer();                
            this.timer.Tick += new EventHandler(UpdateClock);
            this.timer.Interval = 1000;
            this.timer.Enabled = true;
            this.timer.Start();
            UpdateClock(null, null);

            try
            {
                //exception thrown here as this.Color is null
                Color color = (Color)ColorConverter.ConvertFromString(this.Color);
                tbClock.Foreground = new SolidColorBrush(color);                                    
            }
            catch (Exception ex)
            {
                Console.WriteLine(">>>" + ex.Message);
            }
        }

        private void UpdateClock(object sender, EventArgs e)
        {
            tbClock.Text = DisplayString;
        }
    }
}

I'm using it on another page like this:

<CustomControls:DigitalClock color="#ff000000" />

There are no syntax errors and the clock appears on the screen, but whenever the code hits the line where it's trying to set the colour, I just get an Object reference is not set to an instance of an object.

I assume this is something to do with the point in time at which the Color property is set, since after the first 'tick' of the timer, the value is no longer null. How do I get around this?

Arj
  • 1,981
  • 4
  • 25
  • 45
  • Try to set default value to Color in constructor. – Yuri Dorokhov Feb 17 '15 at 14:19
  • Why aren't you just setting the color in the XAML? Or just set it in the constructor. Also, if you do it the way you have already, couldn't you throw in a line right before assigning it: if (color!=null) – Theodosius Von Richthofen Feb 17 '15 at 14:20
  • Try adding the Usercontrol Loaded method and set it there ? – EaziLuizi Feb 17 '15 at 14:23
  • 2
    Your XAML gets executed similar to this: `var ctl = new DigitalClock(); ctl.Color = #ff000000;`. The constructor code runs in the first line, at which time the Color property is `null`. Try using the `Loaded` event instead, or the setter for the `Color` property. Also, I would investigate using a DependencyProperty to allow for binding too :) – Rachel Feb 17 '15 at 14:25
  • Duplicate of http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it – Patrick Hofman Feb 17 '15 at 14:26
  • Thanks, as several of you have pointed out, the `Loaded` event seems to work. The property values have been set by that point. @Rachel, I will indeed look at DependencyProperties, thanks! – Arj Feb 17 '15 at 14:30

3 Answers3

4

When you insert your control inside another XAML document, the properties that are set from this document will be set after your control is intantiated, which means at the time your constructor is invoked the Colorproperty that you may have set in your other XAML document still has its default value.

To do what you want you can either:

  • Listen for the Loaded event of your control (see https://msdn.microsoft.com/en-us/library/ms742302%28v=vs.110%29.aspx), this will be invoked after all the properties of the control instance are set (you may want to start your timer here, and stop it in the Unloaded event to make sure it doesn't tick when the control isn't instantiated on screen),

  • You can write a body for the setter of your Color property to propagate the change:

public string Color
{
  set { tbClock.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(value)); }
}
  • If you want to set the color from another XAML document, you can also provide a property of type Brush instead of color:
public Brush ClockForeground
{
    get { return tnClock.Foreground; }
    set { tnClock.Foreground = value; }
}

So that in your other XAML document, you can set the color directly by letting the XAML parser translate the color name into a brush automatically:

<local:DigitalClock ClockForeground="Yellow" />
  • Or better, you can declare a dependency property on your control and use data binding (assuming here tbClock is a TextBlock):
public Brush ClockForeground
{
    get { return (Brush)GetValue(ClockForegroundProperty); }
    set { SetValue(ClockForegroundProperty, value); }
}
public static readonly DependencyProperty ClockForegroundProperty = DependencyProperty.Register("ClockForeground", typeof(Brush), typeof(DigitalClock));

public DigitalClock()
{
    InitializeComponents();
    ...
    BindingOperations.SetBinding(tbClock, TextBlock.ForegroundProperty, new Binding
    {
        Source = this,
        Path = new PropertyPath(ClockForegroundProperty)
    });
}
2

You should not declare properties as CLR properties. You should create instead Dependency Properties which in default allows you binding, validation and many, many more. Check this out: http://www.codeproject.com/Articles/32825/How-to-Creating-a-WPF-User-Control-using-it-in-a-W. In your example handle event Loaded like this:

  this.Loaded += DigitalClock_Loaded;
 void DigitalClock_Loaded(object sender, RoutedEventArgs e)
        {
                //your actions
                Color color = (Color)ColorConverter.ConvertFromString(this.Color);
        }

Properties is not yet bound in constructor.

MistyK
  • 6,055
  • 2
  • 42
  • 76
  • Thanks, this seems to work. WPF must bind the properties after the constructors, as you mention. – Arj Feb 17 '15 at 14:28
1

Instead of asigning the color in the constructor, since it will be always null because the property will not be set until object is instantiated, it seems better to use the setter of the Color property, using a backing field.

Also, note that you should use Dependency Properties in this case, and take advantage of binding.

Natxo
  • 2,917
  • 1
  • 24
  • 36