Preamble: I am making a logging tool for our company needs. We use MVVM - pattern in our projects via Caliburn. At this moment my tool is capable of weaving compiled code via Mono.Cecil assembly and injecting call of a logging method in public properties set methods.
Fabula: Let's say, I have a class Person
public class Person
{
public string Name
{
get { return _name; }
set
{
Logger.MyLogMethod(_name, value, "Person");
_name = value;
}
}
private string _name;
}
and a class Logger
public static class Logger
{
public static void MyLogMethod(string oldvalue, string newvalue, string propertyname)
{
//condition to log only user changes
}
}
And an instance of a person has its Name property binded to textbox
<TextBox Text="{Binding SelectedPerson.Name}"/>
And we have a button, which click calls a code, which will set new value to SelectedPerson.
So, we have two ways of setting our Person's Name property at a runtime. It's either by user typing in textbox, or a user clicking a button, therefore calling some code.
I would like to know: how to distinct these two cases. In other words - when a set method of a property is called - how to determine, if it was called by binding engine or by our code.
So far I have one approach that is doable in my opinion - to take a look at StackTrace. Not sure how to do it thought.
You can make the Name property private. Then you expose a public property for binding and another for the rest of the application.
You can know differentiate a call from binding from a call from somewhere else
public class Person
{
string NameForOthers
{
get { return Name; }
set
{
Logger.MyLogMethod(_name, value, "Person name set by other place");
Name= value;
}
}
public string NameForBinding
{
get { return Name; }
set
{
Logger.MyLogMethod(_name, value, "Person name set by binding");
Name= value;
}
}
string Name
{
get { return _name; }
set
{
Logger.MyLogMethod(_name, value, "Person");
_name = value;
}
}
private string _name;
}
Then you use it for your binding
<TextBox Text="{Binding SelectedPerson.NameForBinding}"/>
Related
I want to write a control derived from CheckedListBoxControl (DevExpress), and I need to add a property to the (DataBindings)
These are the standard Properties shown in the PropertyGrid:
So I can only choose between Tag and Text.
What I want is to add a third option called gttMasterField (which will be of type int, don't know if this matters)
I have been experimenting with the documentation but with no results.
These don't seem to cover exact what I am looking for, I don't know the correct search terms to find this, which makes it difficult to google for it. It will probably be somewhere in the documentation but also there I don't know on what terms to look for.
Create a Windows Forms user control that supports simple data binding
Create a Windows Forms user control that supports lookup data binding
Create a Windows Forms user control that supports complex data binding
Here is some code with comments that will also help to explain what I am searching for
public partial class gttDXManyToManyCheckedListBox : CheckedListBoxControl
{
private int _gttMasterField;
// This I want populated by setting the binding property MasterField
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
}
The project is a dotnet framework 4.7.2
To make a custom Property appear in the PropertyGrid's (DataBindings), decorate the Property with the BindableAttribute set to true:
[Bindable(true)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
Optionally also decorate with the desired DesignerSerializationVisibilityAttribute attribute
[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
The class can also specify the default bindable Property, settings a DefaultBindingPropertyAttribute:
[DefaultBindingProperty("gttMasterField")]
public partial class gttDXManyToManyCheckedListBox : CheckedListBoxControl
{
private int _gttMasterField;
[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
}
Important note:
the class should implement INotifyPropertyChanged -> a bindable Property is supposed to raise notification events. When the binding is set in the Designer, a BindingSource is generated to mediate the binding, but it requires that the objects involved send change notifications (mostrly to determine when the Property value is updated, usually as DataSourceUpdateMode.OnPropertyChanged).
For example:
using System.Runtime.CompilerServices;
[DefaultBindingProperty("gttMasterField")]
public partial class gttDXManyToManyCheckedListBox : CheckedListBoxControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _gttMasterField;
[Bindable(true)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; NotifyPropertyChanged(); }
}
private void NotifyPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
Setting DesignerSerializationVisibility.Content implies initialization. If a BindingSource is used, this object supports initialization on itself, the Attribute is not strictly required.
It could be set to DesignerSerializationVisibility.Hidden, though, depending on the use case.
I am new to ObjectListView and I haven't found how to use a property of a object inside the model as AspectName for ObjectListView.
For example, the list will show Cars names and their owner name
public class Person
{
private string _name;
public string Name
{
get => _name;
set => _name = value;
}
}
public class Car
{
private Person _owner;
private string _name;
public Person Owner
{
get => _owner;
set => _owner = value;
}
public string Name
{
get => _name;
set => _name = value;
}
}
If I set the AspectName of a column to Owner.Name it doesn't show anything on the list. I have made a small workaround of defining a property OwnerName inside the Car model, but I have more complex models which is a pain to have to define that property.
Is there a way I can do that?
Thanks
Based on what you have said then this should work. So there must be some other problem which is causing this issue.
You could try setting the AspectGetter programmatically instead such as this;
this.olvColumn2.AspectGetter = delegate (Object obj)
{
Car car = (Car)obj;
if (car == null)
return "Unknown";
else
return car.Owner.Name;
};
You can then put a breakpoint inside this code and see when/if it is called and what happens.
Remember that you must define/set all of the aspect getters etc. before you call objectListView1.SetObjects
I've been struggling for a while now with how to best "interact" with settings on a binding level in a WinRT/UWP app. I've been looking for best practices regarding this, but I haven't found clear answers. What I've done so far in my apps is the following:
Define a BindableBase which implements INotifyPropertyChanged.
Create an AppSettings class which inherits from BindableBase and looks a bit like this:
public class AppSettings : BindableBase
{
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
public string MySetting
{
get
{
if (!localSettings.Values.ContainsKey("MySetting"))
localSettings.Values["MySetting"] = "Some default value";
return localSettings.Values["MySetting"].ToString();
}
set
{
localSettings.Values["MySetting"] = value;
RaisePropertyChanged();
}
}
}
Define a ViewModel which has a an AppSettings property:
public class SettingsViewModel
{
public AppSettings Settings { get; set; } = new AppSettings();
}
Bind to the Settings property in the View:
<TextBlock Text="{Binding Settings.MySetting, Mode=TwoWay}">
I've seen and used implementations in the past that had a settings service, but these applications didn't need the settings changes to go in effect immediately. So what I'm basically asking is: If changes to settings should go immediately into effect, is the above implementation a good way to bind to settings? If not, what do you recommend?
I was very inspired by this article so I came up with something a tad bit more elegant to avoid having to copy and paste the property name (key) so many times. I use the following:
public class SettingsViewModel : BindableBase
{
public string MyProperty
{
get
{
return Get("MyProperty", "SomeDefaultValue");
}
set
{
Set("MyProperty", value);
}
}
public T Get<T>(string PropertyName, T DefaultValue)
{
//If setting doesn't exist, create it.
if (!App.Settings.ContainsKey(PropertyName))
App.Settings[PropertyName] = DefaultValue;
return (T)App.Settings[PropertyName];
}
public void Set<T>(string PropertyName, T Value)
{
//If setting doesn't exist or the value is different, create the setting or set new value, respectively.
if (!App.Settings.ContainsKey(PropertyName) || !((T)App.Settings[PropertyName]).Equals(Value))
{
App.Settings[PropertyName] = Value;
OnPropertyChanged(PropertyName);
}
}
}
Assuming your App class defines the following property:
public static IPropertySet Settings
{
get
{
return Windows.Storage.ApplicationData.Current.LocalSettings.Values;
}
}
The latter is more convenient than writing,
ApplicationData.Current.LocalSettings.Values
And can still be accessed globally.
This seems fine in a general case. The only thing I'd change would be to only raise the notification if the data actually changes:
set
{
if (MySetting != value)
{
localSettings.Values["MySetting"] = value;
RaisePropertyChanged();
}
}
This will avoid raising notifications unnecessarily.
Depending on the frequency the values are read, you may want to keep your own in memory copy of the setting values (in a private field) instead of reading them from the container each time.
So I have a WPF MVVM application with a datagrid, and I'm having some trouble setting up the datagrid. I have simplified down the model to make this easier to see the issue, but try to assume that the model can not change (unless for some reason it absolutely has to). I have an observable collection of a custom object as my item source. Let's call the object MyObject. So I have this as my datagrid's itemsource:
ObservableCollection<MyObject> myObjectCollection;
Each object contains an observable collection of another type of custom object. Let's call these new objects MyObjectElement. So each MyObject has the property:
ObservableCollection<MyObjectElement> myObjectElementCollection;
Each MyObjectElement has 3 properties:
string Name;
string Element;
bool IsField;
It can safely be assumed that each MyObject has the same number of MyObjectElements in the myObjectElementCollection, and they all have matching Name and IsField properites. I want a column for each MyObjectElement in a MyObject's myObjectElementCollection only if the IsField property is set to true. Element is the value of each cell.
Now that is the first part of the problem: getting that DataGrid to generate with support for two-way binding. The second part of this problem is the DataGrid Column Types.
I have 2 classes, MyObjectImage and MyObjectTextBox. These both inherit from MyObjectElement. The collection myObjectElementCollection in MyObject does not actually contain any MyObjectElements. It only contains MyObjectImage and MyObjectTextBox objects. If the type of of MyObjectElement is MyObjectTextBox, then it should just show the string.
However, if the type is MyObjectImage the datagrid column should show an image. MyObjectImage contains a property that is a string called ImageType. This string will always be 1 of three values: "PNG", "XAML", or "SVG". If it is equal to "PNG", it is safe to assume that Element is a base64 string of the png. If it is equal to "XAML" it is safe to assume that the image is stored as a large XAML string in Element. If it is equal to "SVG", then it is safe to assume that the image would be an svg as a string, which I do have a converter function that will turn that into a XAML string. The user would change out the image via double clicking on the cell, though that is semi-irrelevant to the problem. It is also safe to assume that the ImageType property will match with it's associated ImageTpe properties in the other myObjectElementCollections.
If you are able to input any advice to any part of this datagrid problem, that would be awesome! I've been banging my head on this for weeks! To make things a bit easier, here is some sample code of the model's structure (it is safe to assume that INotifyPropertChanged is implemented in the class ObservableObject):
My Object
public class MyObject : ObservableObject
{
private ObservableCollection<MyObjectElement> _MyObjectElements;
public ObservableCollection<MyObjectElement> MyObjectElements { get { return _MyObjectElements; } set { if (_MyObjectElements!= value) { _MyObjectElements= value; RaisePropertyChanged("MyObjectElements"); } } }
}
MyObjectElement
public class MyObjectElement : ObservableObject
{
private string _Name;
private string _Element;
private bool _IsField;
public string Name { get { return _Name; } set { if (_Name != value) { _Name = value; RaisePropertyChanged("Name"); } } }
public string Element { get { return _Element; } set { if (_Element != value) { _Element = value; RaisePropertyChanged("Element"); } } }
public bool IsField { get { return _IsField; } set { if (_IsField != value) { _IsField = value; RaisePropertyChanged("IsField"); } } }
}
MyObjectImage
public class MyObjectImage : MyObjectElement
{
private string _ImageType;
public MyObjectImage() : base()
{
}
public MyObjectImage(string name, string element, string imageType) : base(name, element)
{
ImageType = imageType;
}
public string ImageType { get { return _ImageType; } set { if (_ImageType != value) { _ImageType = value; RaisePropertyChanged("ImageType"); } } }
}
MyObjectTextBox
public class MyObjectTextBox : MyObjectElement
{
public MyObjectTextBox() : base()
{
}
public MyObjectTextBox(string name, string element) : base(name, element)
{
}
}
Again, any and all help is appreciated with this problem; Thank you!
I was able to accomplish the first part of the problem through a method that can be found here. The second part of this problem should be able to be accomplished through templating the incoming items.I can easily create an image property that does a conversion to a bitmap image from any of those as the display for the datagrid column.
I have a simple dependency property. I have a user control with a button. I want the caller to set a property called MyName to "Hello". I want the dependency property code to append the string " World" to the end. Here's my code that attempts this:
#region MyNAME DEPENDENCY PROPERTY
private string _myName;
public string MyName
{
get { return this._myName; } //a breakpoint here never trips
set
{
this._myName = value;
this.OnPropertyChanged("MyName");
}
}
public string MyName2 { get { return this._myName; } } //a breakpoint here will trip
public static readonly DependencyProperty MyNameProperty = DependencyProperty.Register("MyName", typeof(string), typeof(MyTabControl), new PropertyMetadata("", OnMyNamePropertyChanged));
private static void OnMyNamePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
MyTabControl myUserControl = dependencyObject as MyTabControl;
myUserControl.OnMyNamePropertyChanged(e);
myUserControl.OnPropertyChanged("MyName");
}
private void OnMyNamePropertyChanged(DependencyPropertyChangedEventArgs e)
{
this.MyName = (string)e.NewValue + " World";
}
#endregion //end MyNAME
And my button XAML code looks like:
<Button Content="{Binding Path=MyName, Mode=OneWay}" />
When I load a page that has this control like this:
<local:MyTabControl MyName="Hello" />
I get a button that says "Hello" rather than a button that says "Hello World". The strange part is if I change my button XAML code to use MyName2 like so:
<Button Content="{Binding Path=MyName2, Mode=OneWay}" />
it works. The getter code is the same (I included it above). I'm guessing that the code that gets _myName is getting called earlier than I expect. What I also don't expect is that the MyName getter never seems to get called. When I put a breakpoint there, it doesn't trip. When I change the button binding path to use MyName2 however, a breakpoint put on that getter will trip.
What am I doing wrong here? How can I use the MyName property and have my button show "Hello World" (without using a second getter of course)
EDIT: Adding new code.
I took both of your suggestions and changed my C# code to simply this:
public string MyName
{
get { return (string)GetValue(MyNameCustomProperty); }
set { SetValue(MyNameCustomProperty, value + " World"); }
}
public static DependencyProperty MyNameCustomProperty = DependencyProperty.Register("MyName", typeof(string), typeof(MyTabControl));
(I'm still new to depency properties) I am still having the same problem as I had with my previous code. Can I do something with this code?
It looks like I need to coerce the value. Here is how I do that:
public string MyName
{
get { return (string)GetValue(MyNameCustomProperty); }
set { SetValue(MyNameCustomProperty, value); }
}
public static DependencyProperty MyNameCustomProperty = DependencyProperty.Register("MyName", typeof(string), typeof(MyTabControl), new PropertyMetadata("", MyPropertyChanged, CoerceCurrentReading));
private static void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//contains coerced value
}
private static object CoerceCurrentReading(DependencyObject d, object value)
{
MyTabControl tab = (MyTabControl)d;
return (string)value + " World";
}
This looks like the cleanest way to change the value entered by the XAML code from "Hello" to "Hello World".
Part of the problem here is that you're not using a real dependency property. In order to use a dependency property you need to use the actual dependency property storage and not a field. This backing storage is where items like binding go to for their values. Change your code to the following and delete the backing field
public string MyName
{
get { return (string)this.GetValue(MyNameProperty); }
set { this.SetValue(MyNameProperty, value); }
}
You want to change what value is set before the notification occurs. The simplest solution is to set the value of the backing field to what you want, then notify:
public string MyName
{
get { return this._myName; } //a breakpoint here never trips
set
{
this._myName = (string)value + "World!";
this.OnPropertyChanged("MyName");
}
}
The problem you are having is that you set the backing field.
Then you notify, which tells the UI to update, as well as triggering OnNotifyPropertyChanged.
OnNotifyPropertyChanged is called after the property has changed, where you try to change the backing field.