I want to get a reference to the TextBox bound to a given property name.
I would like to do this without changing the view.
Is there a proper way to do this using Caliburn Micro?
If not, what is a "good enough" way?
public class MweViewModel : PropertyChangedBase
{
public MweViewModel() : base()
{
PropertyChanged += (object sender, PropertyChangedEventArgs e) =>
{
// Find control (i.e. TextBox) bound to property with name e.PropertyName
TextBox textBox = ...
};
}
}
I'm not necessarily sure this is the most sensible approach (it's not something I've tried to do myself), looking at the Documentation, there's the mention of a ViewModelBinder class that's responsible for fixing up the various bindings of properties, methods, etc. to their respective ViewModels.
The BindProperties function on the ViewModelBinder is responsible for resolving the bindings between your properties, and the UI elements to which they're eventually bound. You could, define your own function based on the existing code, which kept track of all the bindings being established, so you would have a record of them you could use elsewhere in your program.
Using the existing code would give you something like this:
ViewModelBinder.BindProperties = (namedElements, viewModelType) =>
{
var unmatchedElements = new List<FrameworkElement>();
foreach (var element in namedElements)
{
var cleanName = element.Name.Trim('_');
var parts = cleanName.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
var property = viewModelType.GetPropertyCaseInsensitive(parts[0]);
var interpretedViewModelType = viewModelType;
for (int i = 1; i < parts.Length && property != null; i++)
{
interpretedViewModelType = property.PropertyType;
property = interpretedViewModelType.GetPropertyCaseInsensitive(parts[i]);
}
if (property == null)
{
unmatchedElements.Add(element);
// Log.Info("Binding Convention Not Applied: Element {0} did not match a property.", element.Name);
continue;
}
var convention = ConventionManager.GetElementConvention(element.GetType());
if (convention == null)
{
unmatchedElements.Add(element);
// Log.Warn("Binding Convention Not Applied: No conventions configured for {0}.", element.GetType());
continue;
}
var applied = convention.ApplyBinding(
interpretedViewModelType,
cleanName.Replace('_', '.'),
property,
element,
convention
);
if (applied)
{
// Log.Info("Binding Convention Applied: Element {0}.", element.Name);
}
else
{
// Log.Info("Binding Convention Not Applied: Element {0} has existing binding.", element.Name);
unmatchedElements.Add(element);
}
}
return unmatchedElements;
};
At the point when the binding is being added (when applied is set), you have all the information you require. You could then store specific bindings (e.g those relating to a TextBox).
You might use something like a static dictionary (there may be something far more appropriate depending on your requirement):
ViewModel Type Bound Property List of Bound elements
| | |
| | |
Dictionary<Type, Dictionary<PropertyInfo, List<FrameworkElement>>>
You would have to be careful about null/sanity checks.
There are a few other solutions that use helper methods to grab the bound properties/controls, although they often have to traverse the visual tree, this way, you're doing it at the point the binding is actually created.
Related
This will be easiest if I first explain my code structure, then try to ask my question.
I have a base class containing some simple properties. We will call this BaseClass. I then have several other classes that extend BaseClass. Let's call these SubClass1, Subclass2, and SubClass3. Now I have this view model, and it contains this private member:
private ObservableCollection<BaseClass> objs = new ObservableCollection<BaseClass>();
The initializer of the view model contains something like this:
objs.Add(new SubClass1(attribute1, attribute2));
objs.Add(new SubClass3(attribute1, attribute2));
objs.Add(new SubClass1(attribute1, attribute2));
objs.Add(new SubClass2(attribute1, attribute2));
Now, in this case, I have 3 separate datagrids. I want all three to draw from objs, but I want one to show only objects of type SubClass1, one to show only objects of type SubClass2,and the last to show only objects of type SubClass3. I have successfully achieved this by creating a property for each as follows:
public ObservableCollection<SubClass1> SubClass1Objs
{
get
{
ObservableCollection<SubClass1> subObjs = new ObseleCollection<SubClass1>();
if (objs != null)
foreach (BaseClass obj in objs)
if(obj.GetType() == typeof(SubClass1))
subObjs .Add((SubClass1)obj);
return subObjs ;
}
}
The other 2 are identical. I bind the ItemSource of each datagrid to their property. This all works.
The user can currently edit any item in the data grid and the changes are reflected in objs. However, if the user tries to add an item to the datagrid, the items are not added to objs, and rightly so; I have no mutators(setters) for my properties. This is where my issues is. I am having trouble coming up with what the mutators would look like to add a new object to the collection. Does any one have any ideas? Thanks!
Thanks to #user49104 I was able to figure it out. I will post the answer here for anyone else who needs it.
I changed the properties of my SubClass1, SubClass2, and SubClass3 Collections to have normal more normal accessors and mutators and created a private member for each:
private ObservableCollection<SubClass1> _SubClass1Objs ;
public ObservableCollection<SubClass1> SubClass1Objs {
get { if (_SubClass1Objs== null) _SubClass1Objs = new ObservableCollection<SubClass1>(); return _SubClass1Objs; }
set { if (_SubClass1Objs!= value) { _SubClass1Objs= value; RaisePropertyChanged("SubClass1Objs"); } }
}
In the initializer of my view model, set the SubClass data :
SubClass1Objs= new ObservableCollection<SubClass1>();
if (objs != null)
foreach (BaseClass obj in objs.Where(c => c.GetType() == typeof(SubClass1)))
SubClass1Objs.Add((SubClass1)card);;
SubClass1Objs.CollectionChanged += SubClass1Objs_CollectionChanged;
Then lastly, in the CollectionChanged event, I check through to make sure no objects have been added or deleted and fix up objs:
// Check if a card was added
foreach (SubClass1 obj in SubClass1Objs)
if (!objs.Contains(obj))
objs.Add(obj);
// Check if a card has been deleted
for (int i = 0; i < objs.Where(c => c.GetType() == typeof(SubClass1)).Count(); ++i)
{
BaseClass obj = objs.Where(c => c.GetType() == typeof(SubClass1)).ElementAt(i);
if (!SubClass1Objs.Contains(obj))
objs.Remove(obj);
}
I want to analyse a C# class using Roslyn and intend to do something when visited property has the specific attribute applied to it. How can I do this in the CSharpSyntaxWalker.VisitPropertyDeclaration method override?
For example, in the following code block I want to know whether the Date property has the Validation attribute or not, and if so, whether IsJDate is true or false?
[Validation(IsJDate=true)]
public string Date {get; set;}
Initializations:
filesPath.ToList().ForEach(csFilePath =>
{
SyntaxTree csSyntaxTree = CSharpSyntaxTree.ParseText(csFileSourceCode);
// ....
}
_compiledCsCodes = CSharpCompilation.Create("CSClassesAssembly", csFiles.Select(cs => cs.CSSyntaxTree ), references);
foreach (CsFile csFile in csFiles)
{
csFile.FileSemanticModel = _compiledCsCodes.GetSemanticModel(csFile.FullSyntaxTree);
}
Finally, I found the solution by making some changes to Yuriy's answer as following:
foreach (var attribute in node.AttributeLists.SelectMany(al => al.Attributes))
{
if (csFile.FileSemanticModel.GetTypeInfo(attribute).Type.ToDisplayString() == "Proj.Attributes.ValidationAttribute")
{
var arg = attribute.ArgumentList.Arguments.FirstOrDefault(aa => aa.NameEquals.Name.Identifier.Text == "IsJDate");
if (arg != null && arg.Expression.IsKind(SyntaxKind.TrueLiteralExpression))
validationKind = ValidationKind.JDate;
}
}
Use the semantic model to get the bound ISymbol for the property, then call GetAttributes().
We have run into a problem where
We have two instances of the same window in an MDI workspace bound to two separate object models.
The object models have their .Equals and .GetHashCode methods overwritten to be considered equal.
Calling .EndCurrentEdit() on window 2 is triggering a binding update for Window 1
Both windows are setup to use separate a BindingContext
We have discovered the problem has to do with calling
((PropertyManager)ctrl.BindingContext[dataSource]).EndCurrentEdit();
If we change that to
((PropertyManager)ctrl.BindingContext[dataSource, dataMember]).EndCurrentEdit();
It works correctly. It also works correctly if we remove our .Equals and .GetHashCode overrides so the two object models are no longer considered equal.
That doesn't make sense to me because the windows are the same, so the dataMember property would be the same too.
From this link, I believe the definition of these calls is:
public BindingManagerBase this[object dataSource] {
get {
return this[dataSource, ""];
}
}
public BindingManagerBase this[object dataSource, string dataMember] {
get {
return EnsureListManager(dataSource, dataMember);
}
internal BindingManagerBase EnsureListManager(object dataSource, string dataMember) {
BindingManagerBase bindingManagerBase = null;
if (dataMember == null)
dataMember = "";
// Check whether data source wants to provide its own binding managers
// (but fall through to old logic if it fails to provide us with one)
//
if (dataSource is ICurrencyManagerProvider) {
bindingManagerBase = (dataSource as ICurrencyManagerProvider).GetRelatedCurrencyManager(dataMember);
if (bindingManagerBase != null) {
return bindingManagerBase;
}
}
// Check for previously created binding manager
//
HashKey key = GetKey(dataSource, dataMember);
WeakReference wRef;
wRef = listManagers[key] as WeakReference;
if (wRef != null)
bindingManagerBase = (BindingManagerBase) wRef.Target;
if (bindingManagerBase != null) {
return bindingManagerBase;
}
if (dataMember.Length == 0) {
// No data member specified, so create binding manager directly on the data source
//
if (dataSource is IList || dataSource is IListSource) {
// IListSource so we can bind the dataGrid to a table and a dataSet
bindingManagerBase = new CurrencyManager(dataSource);
}
else {
// Otherwise assume simple property binding
bindingManagerBase = new PropertyManager(dataSource);
}
}
else {
// Data member specified, so get data source's binding manager, and hook a 'related' binding manager to it
//
int lastDot = dataMember.LastIndexOf(".");
string dataPath = (lastDot == -1) ? "" : dataMember.Substring(0, lastDot);
string dataField = dataMember.Substring(lastDot + 1);
BindingManagerBase formerManager = EnsureListManager(dataSource, dataPath);
PropertyDescriptor prop = formerManager.GetItemProperties().Find(dataField, true);
if (prop == null)
throw new ArgumentException(SR.GetString(SR.RelatedListManagerChild, dataField));
if (typeof(IList).IsAssignableFrom(prop.PropertyType))
bindingManagerBase = new RelatedCurrencyManager(formerManager, dataField);
else
bindingManagerBase = new RelatedPropertyManager(formerManager, dataField);
}
My dataSource is not an ICurrencyManagerProvider
What is the difference between these two calls, and why does accessing the PropertyManager by only the dataSource result in the bindings for another window with a separate BindingContext being updated?
You don't state this explicitly, so in case you haven't noticed it is the collection look up that is not working as you expect, because of the equal override.
BindingContext[datasource] is a lookup against the collection using datasource as the key.
BindingContext[datasource, datamember] is a lookup against the collection using a composite key.
It is clear from the code that BindingContext is maintaining two separate collections. One a collection on a datasource key and the other a collection based on a composite key.
Obviously your override of equal will twice place similar values to datasource in the BindingContext[datasource] collection, but will result in one collection entry, because the values/keys are the same. Whereas, it will place two entries in BindingContext[datasource, datamember].
If you inspect both collections and can get the counts you'll see that the later collection has more entries.
You have to remember that you have two separate objects that evaluate to equal, not two references to the same object. This is the crux of the problem.
It would appear that when adding the entries to the second collection (BindingContext[datasource, datamember]) datamember evaluates to unique.
When you accessing BindingContext[dataSource], you actually accessing BindingContext[dataSource, ""]. So, there are no difference except HashCode, which uses both DataSource and DataMember values for calculation, that can be seen in your link.
public override int GetHashCode() {
return dataSourceHashCode * dataMember.GetHashCode();
}
The problem in separate BindingContext objects can be that they are not filled correctly.
Well, I need to repeat same code for many properties.
I've seen examples taking Action delegates, but they don't fit quite well here.
I want something like this: (see explanation below)
Dictionary<Property, object> PropertyCorrectValues;
public bool CheckValue(Property P) { return P.Value == PropertyCorrectValues[P]; }
public void DoCorrection(Property P) { P.Value = PropertyCorrectValues[P]; }
.
I want to have a dictionary containing many properties and their respective "correct" values. (I know it's not well declared, but that's the idea). Properties are not necessarely inside my class, some of them are in objects of different assemblies.
A method bool CheckValue(Property). This method must access the actual value of the property and compare to the correct value.
And a method a void DoCorrection(Property). This one sets the property value to the correct value.
Remember I have many of those properties, I wouldn't like to call the methods by hand for each property. I'd rather iterate through the dicionary in a foreach statement.
So, the main question is in the title.
I've tried the by ref, but properties don't accept that.
Am I obligated to use reflection??? Or is there another option (if I need, reflection answer will be accepted as well).
Is there anyway I can make a dictionary with pointers in C#? Or some kind of assignment that changes the value of variable's target instead of changing the target to another value?
Thanks for the help.
You can do this using reflection. Get a list of the properties on the object of interest with typeof(Foo).GetProperties(). Your PropertyCorrectValues property can have type IDictionary<PropertyInfo, object>. Then use the GetValue and SetValue methods on PropertyInfo to perform the desired operations:
public bool CheckProperty(object myObjectToBeChecked, PropertyInfo p)
{
return p.GetValue(myObjectToBeChecked, null).Equals(PropertyCorrectValues[p]);
}
public void DoCorrection(object myObjectToBeCorrected, PropertyInfo p)
{
p.SetValue(myObjectToBeCorrected, PropertyCorrectValues[p]);
}
In addition to Ben's code I'd like to contribute the following code fragment:
Dictionary<string,object> PropertyCorrectValues = new Dictionary<string,object>();
PropertyCorrectValues["UserName"] = "Pete"; // propertyName
PropertyCorrectValues["SomeClass.AccountData"] = "XYZ"; // className.propertyName
public void CheckAndCorrectProperties(object obj) {
if (obj == null) { return; }
// find all properties for given object that need to be checked
var checkableProps = from props
in obj.GetType().GetProperties()
from corr in PropertyCorrectValues
where (corr.Key.Contains(".") == false && props.Name == corr.Key) // propertyName
|| (corr.Key.Contains(".") == true && corr.Key.StartsWith(props.DeclaringType.Name + ".") && corr.Key.EndsWith("." + props.Name)) // className.propertyName
select new { Property = props, Key = corr.Key };
foreach (var pInfo in checkableProps) {
object propValue = pInfo.Property.GetValue(obj, null);
object expectedValue = PropertyCorrectValues[pInfo.Key];
// checking for equal value
if (((propValue == null) && (expectedValue != null)) || (propValue.Equals(expectedValue) == false)) {
// setting value
pInfo.Property.SetValue(obj, expectedValue, null);
}
}
}
When using this "automatic" value correction you might also consider:
You cannot create a PropertyInfo object just by knowing the property name and independently of the declaring class; that's why I chose string for the key.
When using the same property name in different classes then you might need to change the code that is doing the actual assignment because the type between the correct value and the property type might differ.
Using the same property name in different classes will always perform the same check (see point above), so you might need a syntax for property names to restrict it to a specific class (simple dot notation, doesn't work for namespaces or inner classes, but might be extended to do so)
If needed you can replace the "check" and "assign" part with separate method calls, but it might be done inside the code block as stated in my example code.
My UI is build with lots of user controls that are translatable. Some controls in the usercontrols shouldn't be translated and I want to tag them as such with a [DoNotTranslate]` custom attribute.
in my userControl.designer.cs file
[DoNotTranslate]
private DevExpress.XtraEditors.LabelControl maxLabel;
[DoNotTranslate]
private DevExpress.XtraEditors.LabelControl valueLabel;
//all other controls
The translation function expects a (user)control and then goes through all the child control.Controls to make sure all controls are translated without the need to call the translation function on every single control.
Is it possible to find out if a control has my custom attribute set? The problem is that I don't see how I can get the attribute information in the translation function when i go through all the Controls.
Any advice is greatly appreciated,
Thanks
EDIT: I see now from the code you posted that the attribute is on a property of the control, not on the class defining the control itself. You can try like this:
public IEnumerable<PropertyInfo> GetNonTranslatableProperties(WebControl control)
{
foreach (PropertyInfo property in control.GetType().GetProperties())
{
if(
property
.GetCustomAttributes(true)
.Count(item => item is DoNotTranslateAttribute) > 0)
yield return property;
}
}
otherwise, you can subclass Label to a NonTranslatableLabel, apply the attribute to the class and use it instead of Label in your "father" control.
[NonTranslatable]
public class NonTranslatableLabel : Label
===================
for each of your controls you can do:
myCustomControl.GetType()
.GetCustomAttributes(true)
.Where(item => item is DoNotTranslateAttribute);
for instance you could enumerate all your "non-translatable" controls like this:
public IEnumerable<Control> GetNonTranslatableChildren(Control control)
{
foreach(Control c in control.Controls)
{
if(
c.GetType()
.GetCustomAttributes(true)
.Count(item => item is DoNotTranslateAttribute) > 0)
yield return c;
}
}
You can use GetCustomAttributes method to find out if the attribute has been applied or not. For example,
static readonly Type _DoNotTranslateAttribute = typeof(DoNotTranslate);
.... // other code
var t = control.GetType();
if (t.GetCustomAttributes(_DoNotTranslateAttribute, false).length > 0)
{
// do not translate
}
(disclaimer: untested/uncompiled code- just to give an idea how to use the function)