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?
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)
});
}
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.
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.
Related
Im having difficulties with passing a Color from the ColorPicker into another sheet.
Im attempting to store the Brush as a variable so i can pass this variable into another sheet.
As another person pointed out on my last post, every time i navigate away from a page, the data is cleared from the settings page and thus, there is no way for my "MainPage" to retrieve the brush.
UseCase:
Whenever I change the color on Settings Page tht should be reflected on my MainPage.
Im not sure how to implement this at all and the documentation on this is mainly only for using it locally.
I have the following event trigger on ColorChange within thge Settings_Page.xaml:
private void TextColourPicker_ColorChanged(ColorPicker sender, ColorChangedEventArgs args)
{
}
I was thinking of doing something like i had to do with one of my GlobalVariables. This involved a GlobalVariables_PropertyChanged event. The Colour event seems different though.
I have created a Static Class within my GlobalVariableStorage Class as follows:
public static class ColourSelections
{
private static Brush _TextColour;
public static Brush TextColour
{
get { return _TextColour; }
set { _TextColour = value; NotifyPropertyChanged(); }
}
I have no idea if this is right or if it helps at all.
My hope was to assign the selected colour to the GlobalVariables text colour brush.
If anyone has any better execution methods for this, please let me know.
I am in the process of trying to work in an MVVM approach but adjusting my code as I go and learn.
I apologise for my vagueness and lack of understanding. Passing brush information between sheets is new to me and im just not sure how to approach it.
Edit:
I have added the following.
On my Settings_Page.xaml.cs:
private void TextColourPicker_ColorChanged(ColorPicker sender, ColorChangedEventArgs args)
{
SolidColorBrush TextColorBrush = new SolidColorBrush(TextColourPicker.Color);
ColourSelections.TextColour = TextColorBrush;
}
This writes to the following class within the GlobalVariableStorage.cs:
public static class ColourSelections
{
private static Brush _TextColour;
public static Brush TextColour
{
get { return _TextColour; }
set { _TextColour = value; NotifyPropertyChanged(); }
}
public static event PropertyChangedEventHandler PropertyChanged;
private static void NotifyPropertyChanged([CallerMemberName]string propertyName = "") =>
PropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
What i now need to do is:
Read the TextColour variable back into Mainpage. The issue is, the thing im trying to change is the NavigationViewItem "Foreground" Colour.
You are doing it the right way using the static class, whenever the color is changed you can assign the value of new color to the variable within your static class and to read it on MainPage just override the OnNavigatedTo method on MainPage and assign the color value to each of your NavigationViewItems there.
protected override void OnNavigatedTo(object sender, object args)
{
if(ColourSelections.TextColor != null)
{
//considering NavigationItem1 is the x:Name of your first NavigtionViewItem.
NavigationItem1.Foreground = ColourSelections.TextColor;
}
}
Note : Make sure the type of TextColor is SolidColorBrush because that is the type of Foreground as well.
so I have a model which contains 2 variables, a List and a DateTime. In my UserControl I have a DependencyProperty and I also defined a PropertyChangedCallback.
public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(List<MyContainer>), typeof(UC), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnMyProperty)));
public List<MyContainer> My
{
get
{
return GetValue(MyProperty) as List<MyContainer>;
}
set
{
SetValue(MyProperty, value);
}
}
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
//do stuff
}
On my form there is a button, which do the changes on the other model variable (on the DateTime).
private void Date_Click(object sender, RoutedEventArgs e)
{
MyModel model = DataContext as MyModel;
if (model != null)
{
model.Date = model.Date.AddDays(1);
}
}
And finally here is my model.
public class MyModel : INotifyPropertyChanged
{
private List<MyContainer> _My;
private DateTime _Date;
public MyModel()
{
_Date = DateTime.Now.Date;
_My = new List<MyContainer>();
}
public List<MyContainer> My
{
get
{
return _My;
}
set
{
_My = value;
OnPropertyChanged("My");
}
}
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
XAML declaration is the following.
<local:UC My="{Binding My}" />
So my problem is the after I hit the run, it fires the OnMyProperty once, after that if I hit the button, it changes the DateTime property well, but the OnMyProperty callback doesn't firing again. However I noticed that if I modify my model like this
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
_My = new List<MyContainer>(_My); //added
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
now it fires it every time when I hit the button. How can I trigger the second behaviour without that modification?
After setting the value of a DependencyProperty it first checks if the new value is different to the old one. Only in this case the PropertyChangedCallback method you registered with that DependencyProperty is called. So the name PropertyChanged makes sense.
In your (not modified) case you not even try to change My (only Date). So there is no reason to raise the callback function.
The answer is that you almost certainly do not need to do this. When you ask a question about how to make the framework do something it really does not want to do, always say why you think you need to do that. It's very likely that there's a much easier answer that everybody else is already using.
The only thing you have bound to the control is My. Therefore, if My hasn't changed, then the state of the control should not change. If you want the state of the control to change when Date changes, bind Date to some property of the control. The only way the control should ever get information from any viewmodel is through binding one of its dependency properties to a property of the viewmodel.
The control should not ever know or care who or what is providing values for its properties. It should be able to do its job knowing only the property values it has been given.
If the contents of My have changed -- you added an item or removed one -- of course the control has no way of knowing that, because you refused to tell it. You're just telling it there's a new list. It checks, sees it's still got the same old list, and ignores you. The My property of your viewmodel should be an ObservableCollection, because that will notify the control when you add or remove items in the collection.
The items themselves, your MyContainer class, must implement INofityPropertyChanged as well, if you want to be able to change their properties while they are displayed in the UI.
The dependency property My on your control must not be of type List<T>. It should probably be type object, just like ItemsControl.ItemsSource. Then your control template can display it in an ItemsControl which knows what to do with it. If an ObservableCollection is bound to it as I suggested above, the ItemsControl will update automatically. In OnMyProperty, your control class can check to see if it's an observable collection as well:
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
if (e.NewValue is INotifyCollectionChanged)
{
(e.NewValue as INotifyCollectionChanged).CollectionChanged +=
(s, ecc) => {
// Do stuff with UC and ecc.NewItems, ecc.OldItems, etc.
};
}
}
I'm trying to monitor a value and when it is changed, to update a text field after performing some calculations with a result.
The value I'm trying to monitor comes from an AGauge property (custom control). I want to update the text field when the AGauge.Value changes.
I've looked at questions such as This One but I don't really understand how this works, or what I need to change to get the result I'm looking for.
Can anyone better explain what I need to do in order for this to work?
The AGuage.Value is a float type, incase your wondering.
Thanks in advance.
Update 1
I have now added the following code to my project:
public class AGuage
{
private float _value;
public float Value
{
get
{
return this._value;
}
set
{
this._value = value;
this.ValueChanged(this._value);
}
}
public void ValueChanged(float newValue)
{
}
}
And can get the ValueChanged to fire using the following:
AGuage n = new AGuage();
n.Value = Pressure_Gauge.Value;
Which fires everytime the Pressure_Gauge.Value is updated.
The issue, or last hurdle, I am facing now is this part:
public void ValueChanged(float newValue)
{
Form1.Pressure_Raw.text = "Working";
}
I want to update the label's text on form1 usingthe above method, however I get an error saying: An object reference is required for the nonstatic field, method, or property.
I'm not sure how to do this, I've read some information about Static properties, but how would I update the label's text value from within this?
Thanks.
This might help. You could add an event and subscribe to it in your form.
For example:
public class AGauge {
// You can either set the Value this way
public float Value {
get {return this.Value;}
set
{
// (1)
// set "Value"
this.Value = value;
// raise event for value changed
OnValueChanged(null);
}
}
// create an event for the value change
// this is extra classy, as you can edit the event right
// from the property window for the control in visual studio
[Category("Action")]
[Description("Fires when the value is changed")]
public event EventHandler ValueChanged;
protected virtual void OnValueChanged(EventArgs e)
{
// (2)
// Raise the event
if (ValueChanged != null)
ValueChanged(this,e);
}
}
public Form1 : Form {
// In form, make your control and add subscriber to event
AGauge ag = new AGauge();
// (3)
ag.ValueChanged += UpdateTextBox;
// (4)
public void UpdateTextBox(object sender, EventArgs e)
{
// update the textbox here
textbox.Text = ag.Value;
}
}
Here's how this works:
At (3) you add a subscriber to the ag.ValueChanged event as described HERE.
When you go to change ag.Value, you get to (1), where Value is changed and OnValueChanged is called. This gets you to (2), where the ValueChanged event is raised. When this happens, all subscribers to that event are "notified" and call their respective methods. So when you get to (2), (4) ends up getting called because "UpdateTextBox" was set as a subscriber to the ValueChanged event. It's a bit tricky, but it is very useful.
Or if you want to continue with how you're trying to do it, you need to do this:
public class AGuage
{
private float _value;
// create object of Form1 for reference
private Form1 form1;
// pass reference to form1 through constructor
public AGauge(Form1 form1)
{
// assign
this.form1 = form1;
}
public float Value
{
get
{
return this._value;
}
set
{
this._value = value;
this.ValueChanged(this._value);
}
}
public void ValueChanged(float newValue)
{
// use the form1 reference
this.form1.Pressure_Raw.Text = "Working";
}
}
And then do this:
// if creating the AGauge object in Form1, pass "this" to the object
AGuage n = new AGuage(this);
I highly recommend you don't do it this way as this breaks the generics rule for OOP. Which means, if you try to use this AGauge control anywhere else other than in Form1, it will not work the same way. I recommend doing it with events like I have described above. It's much more universal.
You need to make your AGauge implement INotifyPropertyChanged and notify the property changing on Value. There's enough information on Google on how to do this and has been discussed hundreds of times in StackOverflow.
Then, you will need to use a Binding to bind your textbox to the AGauge value. Since you need to convert, you'll need to provide formatting and optionally parsing.
This should be something like:
var binding = new Binding("Text", myAgaugeControl, "Value");
binding.Format += BindingFormat;
binding.Parse += BindingParse;
myTextBox.DataBindings.Add(binding);
BindingFormat and BindingParse should be the converters. Format would be for converting the gauge's value to the textbox string. The most simple:
void BindingFormat(object sender, ConvertEventArgs e)
{
e.Value = e.Value.ToString();
}
BindingParse would be the opposite: if the textbox text changes, you need to parse the text and convert it to a value AGauge can understand. I'll let you figure this out.
More information on Binding, Format and Parse
What you need to do is create a custom setter for the Value property. Every time the value is set your code will call your hook method which I called ValueChanged(). In that method you can perform your calculations and then set the text field to the result.
public class AGuage
{
private float _value;
public float Value
{
get
{
return this._value;
}
set
{
this._value = value;
this.ValueChanged(this._value);
}
}
public void ValueChanged(float newValue)
{
// Action to perform on value change
// Update a text field after performing some calculations with a result.
}
}
A nice and clean option is to use Microsoft's Reactive Framework (NuGet "Rx-WinForms"). It lets you work with observables (as opposed to enumerables) in a LINQ-like manner.
Your class would look like this:
public class AGuage
{
private float _value;
private Subject<float> _values = new Subject<float>();
public float Value
{
get { return _value; }
set
{
_value = value;
_values.OnNext(value);
}
}
public IObservable<float> Values
{
get { return _values.AsObservable(); }
}
}
Now you can do things like this:
var aGuage = new AGuage();
var query =
from value in aGuage.Values
where value > 5.0f && value < 20.0f //filtering
select value * 150f + 45.3f; //computation
var subscription =
query.Subscribe(value =>
{
/* do something with the filtered & computed value */
});
aGuage.Value = 2.1f; // query.Subscribe doesn't fire
aGuage.Value = 12.4f; // query.Subscribe DOES fire
aGuage.Value = 202.1f; // query.Subscribe doesn't fire
If you want to shut down the subscription to the values just call subscription.Dispose().
I am working on some custom (inherited) controls with custom properties for which I have added design-time support after proper attribute decoration.
All works well but my problem is that the auto-generated code in the *.Designer.cs file where the control is used, has a particular order that it sets the various properties in (both base and new properties). This order looks like it is alphabetical w.r.t. the property name.
So the auto generated code looks like this:
//
// myTabPage1
//
this.myTabPage1.Identifier = "ID";
this.myTabPage1.Name = "myTabPage1";
this.myTabPage1.Size = new System.Drawing.Size(294, 272);
this.myTabPage1.Text = "TTT";
Where I would have liked it to look like this:
//
// myTabPage1
//
this.myTabPage1.Name = "myTabPage1";
this.myTabPage1.Size = new System.Drawing.Size(294, 272);
this.myTabPage1.Text = "TTT";
this.myTabPage1.Identifier = "ID";
The reason I need this is because setting the Identifier property affects Text which is then reverted back to its design-time value, annulling the effect of setting the Identifier.
There are of course simple workarounds (the simplest of which is not to set the Text property which work well) but it would be nice if this was not a design-time "worry", as there is extensive usage of this design pattern in many inherited control types and their instances.
It is also helpful to set Text to identify the controls on the form designer (Identifier has no effect on Text at design-time).
No, you can't affect serialization order. This is otherwise a common problem with a general solution, implement the ISupportInitialize interface. Your BeginInit() method is called just before the designer starts assigning properties, you'd typically set a bool variable that ensures that property setters don't have unintended side-effects. Your EndInit() method is called when it is done and all properties have a value, you'd set the variable back to false and do whatever you need to do to use the values.
Your question isn't specific enough about the control with the problem, but a possible implementation could look like this:
public partial class CustomControl : UserControl, ISupportInitialize {
public CustomControl() {
InitializeComponent();
}
private bool initializing;
private string id = "";
public string ID {
get { return id; }
set { id = value;
if (!initializing) label1.Text = value;
}
}
[Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override string Text {
get { return base.Text; }
set {
base.Text = value;
if (!initializing && !this.DesignMode) label1.Text = value;
}
}
public void BeginInit() {
initializing = true;
}
public void EndInit() {
initializing = false;
label1.Text = ID;
}
}
Also note the [DesignerSerializationVisibility] attribute in this code, when you use Hidden then the property doesn't get serialized at all. That could be a simple solution to your problem.
Say I have a global variable INT named X. Since X is global, we can assume that anything can modify its value so it is being changed everytime.
Say I have a Label control named "label". Here's what I want to accomplish:
I want to "bind" the value of label.Text to variable X. In such a way that when variable X is changed, it will be reflected back to label.Text.
Now, I don't want to write event listeners and play with delegates with this one (I want the least amount of code as possible). Is there a way to use the DataBinding component for this one? or any other novel techniques?
If you want to use the Databinding infrastructure, and reflect the changes made to a value, you need a way to notify the UI about the changes made to the binding value.
So the best way to do that is to use a property and implement the INotifyPropertyChanged interface, like this:
class frmFoo : Form, INotifyPropertyChanged
{
private string _foo;
public string Foo
{
get { return _foo; }
set
{
_foo = value;
OnPropertyChanged("Foo");
}
}
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Also remember that you need to setup the binding on the label first:
public frmFoo()
{
InitializeComponent();
lblTest.DataBindings.Add(new Binding("Text", this, "Foo"));
}
For a multi-threaded program (so almost every windows forms program) iCe's answer is not a good one, because it won't let you change the label anyway (you will get some cross-threading error). The simplest way to fix the problem is creating property in setter:
private string _labelText;
public string labelText
{
get { return _labelText; }
set
{
_labelText = value;
updateLabelText(_labelText); //setting label to value
}
}
where updateLabelText(string) is thread safe:
delegate void updateLabelTextDelegate(string newText);
private void updateLabelText(string newText)
{
if (label1.InvokeRequired)
{
// this is worker thread
updateLabelTextDelegate del = new updateLabelTextDelegate(updateLabelText);
label1.Invoke(del, new object[] { newText });
}
else
{
// this is UI thread
label1.Text = newText;
}
}
I don't think you'd be able to bind to a public variable. A variable by itself doesn't have the ability to notify listeners of a change in its value.
That is why you need to wrap the variable in a property. In the setter you raise an event to notify the UI controls that are bound to it, so that they can refresh and display the new value. The framework has a mechanism for this - INotifyPropertyChanged - try this link for a how-to.
Create a property for X. In setter update the label.Text property.
private int _x;
public int X {
get
{
return _x;
}
set
{
_x = value;
label.Text = _x.ToString();
}
}