I have a Silverlight custom control with two properties; Text and Id. I have created DependencyProperties for these as per the code below.
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(LookupControl), new PropertyMetadata(NotifyPropertyChanged));
public static readonly DependencyProperty IdProperty = DependencyProperty.Register("Id", typeof(Guid?), typeof(LookupControl), new PropertyMetadata(NotifyPropertyChanged));
public event PropertyChangedEventHandler PropertyChanged;
public static void NotifyPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var control = sender as LookupControl;
if (control != null && control.PropertyChanged != null)
{
control.PropertyChanged(control, new PropertyChangedEventArgs("Text"));
}
}
public Guid? Id
{
get { return (Guid?)GetValue(IdProperty); }
set { SetValue(IdProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
In a control method, the Id is populated first, and then the Text. My problem is that when i bind to Text and Id on this control, I want their data to be populated synchronously, so that when a PropertyChanged event fires on either property, both of them have updated data.
At this point in time I catch when the Id has changed, performed some processing, and if required, i set the Text to a new value. But once this OnChange of Id has finished, then the control method continues and populates the Text after i have already changed it back to something else.
Cold you save the values and only set when you have both?
private Guid? id;
private string text;
public Guid?Id
{
get { return id; }
set {
id = value;
TrySetValue();
}
}
public string Text
{
get { return text; }
set { text = value;
TrySetValue()}
}
private void TrySetValue()
{
if (id != null && text != null)
{
SetValue(IdProperty, id);
SetValue(TextProperty, text);
}
}
Related
I want to create a simple Control to enter Text.
For this Control i want to create a Property.
This Property should be bind to the ViewModel.
I Created a Model for an Folder:
public class FolderModel : ModelBase
{
private string fullPath;
public string FullPath
{
get { return fullPath; }
set
{
if (fullPath == value)
return;
fullPath = value;
RaisePropertyChanged(nameof(FullPath));
}
}
}
In my View xaml i bound the Data to my ViewModel:
<UserControl.DataContext>
<Browser:FolderBrowserViewModel/>
</UserControl.DataContext>
<Grid>
<TextBox x:Name="TextBox_Folder" IsReadOnly="True" Text="{Binding Folder.FullPath}"/>
</Grid>
To Create the Property created this code in the View:
public string FullPath
{
get { return (string)GetValue(FolderFullPathProperty); }
set { SetValue(FolderFullPathProperty, value); }
}
public static readonly DependencyProperty FolderFullPathProperty = DependencyProperty.Register("FullPath", typeof(string), typeof(FolderBrowser), new PropertyMetadata("", new PropertyChangedCallback(OnSetTextChanged)));
private static void OnSetTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FolderBrowser UserControl1Control = d as FolderBrowser;
UserControl1Control.OnSetTextChanged(e);
}
private void OnSetTextChanged(DependencyPropertyChangedEventArgs e)
{
TextBox_Folder.Text = e.NewValue.ToString();
}
My ViewModel looks like this:
private ICommand _browseFolderCommand;
public ICommand BrowseFolderCommand
{
get
{
return _browseFolderCommand is null ? (_browseFolderCommand = new RelayCommand(() => BrowseFolder(), true)) : _browseFolderCommand;
}
}
private FolderModel folder = new FolderModel();
public FolderModel Folder
{
get { return folder; }
set
{
if (folder == value)
return;
folder = value;
}
}
private void BrowseFolder()
{
System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog();
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
SetField(ref folder, Folder, "FullPath");
}
}
and my ViewModelBase looks like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected void Dispatch(Action f) => Application.Current.Dispatcher.Invoke(f);
protected TResult Dispatch<TResult>(Func<TResult> f) => Application.Current.Dispatcher.Invoke(f);
}
if i use the Control i want to set the Property "FullPath"
when i set this Property the Value should be set to the ViewModels Folder.FullPath.
If this is set, the Binding of the TextBox and Folder.FullPath should show the correct path.
if its possible i want all Code in the ViewModel =)
Edit:
I tried to illustrate it a bit more pictorially:
On the left side you can see the control as it is placed on a window. There you can set MyProp to any value and the TextBox will receive it.
On the right side I have tried to show it in more detail.
The view has the label, a TextBox and a button.
The view also has the property "MyProp".
The textbox of the view is bound to the ViewModel, namely to the field "MyCoolFieldValue".
This means that if I do anything in the ViewModel with the field MyCoolFieldValue, I know that it will always have the value that is in the textbox.
If I now press the button, a command is called. This command changes the WErt of MyCoolFieldValue. When this happens, the value should be written directly back into the property and the textbox of the view.
However, I can't get this to work and have tried it with the code above.
I have Label databound with BindingSource property. Label.Text property get updated only once.
this is how is property bound to label
this.lblWorkPlace.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.appStateBindingSource, "ResourceName", true));
i also tried to bind same property to textbox and textbox updates properly
this.lTextEdit1.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.appStateBindingSource, "ResourceName", true));
what could be wrong?
UPDATE
this is my "state" class
public class AppState: INotifyPropertyChanged
{
private static Operation _activeTask;
private static AppState _instance;
public static AppState Instance
{
get => _instance ?? (_instance = new AppState());
}
public Operation ActiveTask
{
get => _activeTask;
set
{
if (value != _activeTask)
{
_activeTask = value;
RaisePropertyChanged("ResourceName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string prop)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
public string ResourceName => ActiveTask?.Operacija?.acResursName.Trim() ?? "";
}
}
Problem was cross thread call. Application did not update UI and did not throw any exceptions
I have a dependency property in a user control
public List<Exclusion> SelectedExclusions
{
get { return (List<Exclusion>)GetValue(SelectedExclusionsProperty); }
set { SetValue(SelectedExclusionsProperty, value); }
}
public static readonly DependencyProperty SelectedExclusionsProperty =
DependencyProperty.Register(nameof(TimeSeriesChart.SelectedExclusions), typeof(List<Exclusion>), typeof(TimeSeriesChart), new PropertyMetadata(new List<Exclusion>()));
The list is populated on delete key down:
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.Key == Key.Delete)
{
this.SelectedExclusions.Add(this.Exclusions[this.Index]);
}
}
}
In the view model I new up a list & public property. A delete command is invoked from the setter:
private IList<Exclusion> selectedExclusionsToDelete = new List<Exclusion>();
public IList<Exclusion> SelectedExclusionsToDelete
{
get
{
return this.selectedExclusionsToDelete;
}
set
{
this.selectedExclusionsToDelete = value;
//Delete the selected exclusion
ExecuteDeleteSelectedExclusionsCommand();
this.RaisePropertyChanged();
}
}
Finally, my xaml binding in the view:
SelectedExclusions="{Binding SelectedExclusionsToDelete, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
My problem is that at run time the SelectedExclusions dp getter returns null instead of a List<Exclusion> Is there something I'm missing here given that the PropertyMetadata value is of type List<Exclusion>?
I have a class CustomControl which inherits from System.Windows.Forms.Control.
I will also create a new class named GraphicsData which will have all the graphical information about my CustomControl (I need this because it's easier to serialize the data for saving it in a DB, json, etc.)
The CustomControl object will get the GraphicsData at the initialization(in the constructor) and I want it to get all the properties that have a value in GraphicsData (sometimes I don't want to initialize all of the properties from GraphicsData and I want them to remain the default from System.Windows.Forms.Control class).
The problem is that most of the proprierties are not nullable and I cannot check if they are null so I can't do a simple:
customControl.BackColor = graphicsData.BackColor.HasValue ? graphicsData.BackColor.Value : BackColor;
I can of course work this around if I create my own Nullable class but this got really ugly and hard to understand the code. Also, it is very hard to add a new property when needed.
Now, what I did and I think this is a much cleaner way is the following:
GraphicsData class:
public class GraphicsData : INotifyPropertyChanged
{
private readonly List<string> _initializedProperties = new List<string>();
public List<string> InitializedProperties { get { return _initializedProperties; } }
public event PropertyChangedEventHandler PropertyChanged;
private Size _size;
private Point _location;
private AnchorStyles _anchor;
private Color _backColor;
private Image _backgroundImage;
private Cursor _cursor;
private Font _font;
private Color _foreColor;
private bool _enabled;
private bool _visible;
public Size Size
{
get { return _size; }
set
{
_size = value;
OnPropertyChanged("Size");
}
}
public Point Location
{
get { return _location; }
set
{
_location = value;
OnPropertyChanged("Location");
}
}
public AnchorStyles Anchor
{
get { return _anchor; }
set
{
_anchor = value;
OnPropertyChanged("Anchor");
}
}
public Color BackColor
{
get { return _backColor; }
set
{
_backColor = value;
OnPropertyChanged("BackColor");
}
}
public Image BackgroundImage
{
get { return _backgroundImage; }
set
{
_backgroundImage = value;
OnPropertyChanged("BackgroundImage");
}
}
public Cursor Cursor
{
get { return _cursor; }
set
{
_cursor = value;
OnPropertyChanged("Cursor");
}
}
public Font Font
{
get { return _font; }
set
{
_font = value;
OnPropertyChanged("Font");
}
}
public Color ForeColor
{
get { return _foreColor; }
set
{
_foreColor = value;
OnPropertyChanged("ForeColor");
}
}
public bool Enabled
{
get { return _enabled; }
set
{
_enabled = value;
OnPropertyChanged("Enabled");
}
}
public bool Visible
{
get { return _visible; }
set
{
_visible = value;
OnPropertyChanged("Visible");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (!_initializedProperties.Contains(propertyName))
_initializedProperties.Add(propertyName);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
And in my custom control I have a method:
public void LoadGraphics()
{
var initializedProperties = graphics.InitializedProperties;
foreach (string propertyName in initializedProperties)
{
var value = graphics.GetType()
.GetProperty(propertyName)
.GetValue(graphics, null);
_customControl.GetType()
.GetProperty(propertyName)
.SetValue(_customControl, value, null);
}
}
Basically, I created a List named InitializedProperties and in the properties "set" I add the property in the list.
After that, using reflection in my CustomControl, I can load all the initialized properties.
I implemented the INotifyPropertyChanged because I also want to change the customControl properties each time a property is changed in GraphicsData.
Is this the proper way to do what I want ? I don't think the reflection code is that readable and I am concerned about the performance.
Using nullable values is a much easier method for achieving this.
C# already has a built-in Nullable class, but it also offers a simple way to make a value nullable without the excess verbosity that the Nullable class introduces: ?.
All of your values can be made nullable by appending the ? operator to the value types:
private Size? _size;
private Point? _location;
private AnchorStyles? _anchor;
private Color? _backColor;
private Image _backgroundImage;
private Cursor _cursor;
private Font _font;
private Color? _foreColor;
private bool? _enabled;
private bool? _visible;
Your LoadGraphics method can easily check to see if the GraphicsData property has a non-null value, and set the corresponding control property if so.
public void LoadGraphics(GraphicsData gfx)
{
// It may be permissible to utilize a null value for BackgroundImage!
// In this case, utilizing a separate field (IsBackgroundImageSet) may be a necessary
if (gfx.BackgroundImage != null) { _customControl.BackgroundImage = gfx.BackgroundImage; }
if (gfx.Size != null) { _customControl.Size = gfx.Size.Value; }
if (gfx.Location != null) { _customControl.Location = gfx.Location.Value }
if (gfx.Anchor != null) { _customControl.Anchor = gfx.Anchor.Value; }
if (gfx.BackColor != null) { _customControl.BackColor = gfx.BackColor .Value; }
if (gfx.Cursor != null) { _customControl.Cursor = gfx.Cursor; }
if (gfx.Font != null) { _customControl.Font = gfx.Font; }
if (gfx.Color != null) { _customControl.Color = gfx.Color.Value; }
if (gfx.Enabled != null) { _customControl.Enabled = gfx.Enabled.Value; }
if (gfx.Visible != null) { _customControl.Visible = gfx.Visible.Value; }
}
I have a WPF application using MVVM. I have the IsChecked value bound to a boolean on my model instance on my ViewModel. I also need to bind a method on the ViewModel to the Checked and Unchecked events. (This is so I can track unsaved changes and change the background to give my users visual indication of the need to save. I tried:
<CheckBox
Content="Enable"
Margin="5"
IsChecked="{Binding Enabled}"
Checked="{Binding ScheduleChanged}"
Unchecked="{Binding ScheduleChanged}"
/>
But I get a 'Provide value on 'System.Windows.Data.Binding' threw an exception.' error. Advice?
Here is the Model I am working with:
public class Schedule : IEquatable<Schedule>
{
private DateTime _scheduledStart;
private DateTime _scheduledEnd;
private bool _enabled;
private string _url;
public DateTime ScheduledStart
{
get { return _scheduledStart; }
set
{
_scheduledStart = value;
}
}
public DateTime ScheduledEnd
{
get { return _scheduledEnd; }
set
{
if(value < ScheduledStart)
{
throw new ArgumentException("Scheduled End cannot be earlier than Scheduled Start.");
}
else
{
_scheduledEnd = value;
}
}
}
public bool Enabled
{
get { return _enabled; }
set { _enabled = value; }
}
public string Url
{
get { return _url; }
set { _url = value; }
}
public bool Equals(Schedule other)
{
if(this.ScheduledStart == other.ScheduledStart && this.ScheduledEnd == other.ScheduledEnd
&& this.Enabled == other.Enabled && this.Url == other.Url)
{
return true;
}
else
{
return false;
}
}
}
My viewModel contains a property that has an ObservableCollection. An ItemsControl binds to the collection and generates a list. So my ViewModel sort of knows about my Model instance, but wouldn't know which one, I don't think.
Checked and Unchecked are events, so you can not bind to them like you can IsChecked, which is a property. On a higher level it is also probably wise for your view model not to know about a checkbox on the view.
I would create an event on the view model that fires when Enabled is changed, and you can subscribe to that and handle it any way you like.
private bool _enabled;
public bool Enabled
{
get
{
return _enabled;
}
set
{
if (_enabled != value)
{
_enabled = value;
RaisePropertyChanged("Enabled");
if (EnabledChanged != null)
{
EnabledChanged(this, EventArgs.Empty);
}
}
}
}
public event EventHandler EnabledChanged;
// constructor
public ViewModel()
{
this.EnabledChanged += This_EnabledChanged;
}
private This_EnabledChanged(object sender, EventArgs e)
{
// do stuff here
}
You should be able to just handle this in the setter for Enabled...
public class MyViewModel : ViewModelBase
{
private bool _isDirty;
private bool _enabled;
public MyViewModel()
{
SaveCommand = new RelayCommand(Save, CanSave);
}
public ICommand SaveCommand { get; }
private void Save()
{
//TODO: Add your saving logic
}
private bool CanSave()
{
return IsDirty;
}
public bool IsDirty
{
get { return _isDirty; }
private set
{
if (_isDirty != value)
{
RaisePropertyChanged();
}
}
}
public bool Enabled
{
get { return _enabled; }
set
{
if (_enabled != value)
{
_enabled = value;
IsDirty = true;
}
//Whatever code you need to raise the INotifyPropertyChanged.PropertyChanged event
RaisePropertyChanged();
}
}
}
You're getting a binding error because you can't bind a control event directly to a method call.
Edit: Added a more complete example.
The example uses the MVVM Lite framework, but the approach should work with any MVVM implementation.