How to resolve bound object from bindingexpression with WPF? - c#

Hi does anyone know if there are any inbuilt classes for resolving a bound object from a bindingexpression and it's DataItem and property path?
I'm attempting to write a Blend 3 behavior for textboxes which automatically invokes methods on an object bound to the textbox Text property.
The textbox is bound to a property on a viewmodel class. What I want to do is resolve the viewmodel class from the binding expression and then make calls on this.
I first retrieve the binding expression from the behavior's associated object like so:
private BindingExpression GetTextBinding()
{
return this.AssociatedObject.GetBindingExpression(TextBox.TextProperty);
}
Having done this, if we look at the binding expression, we can see it has a reference to the data context via the binding expression's DataItem property.
In addition, we have the relative path of the property which is bound on the binding expression's parent binding.
So, we can get this information:
var bindingExpression = GetTextBinding();
object dataContextItem = bindingExpression.DataItem;
PropertyPath relativePropertyPath = bindingExpression.ParentBinding.Path;
Now, this property path could potentially be a deeply nested and complex path, which I would very much like to avoid having to (re?)implement resolution of. I've searched around the .NET documentation and bounced around the assemblies with reflector, all to no avail - I can't find what surely must exist - there's got to be some class which performs the resolution of the path for the dataitem (the data context).
Does anyone know where this might exist? Any suggestions for alternative ways of resolving the bound object?
Note, I'm trying to get at the bound object which is the parent of the bound property (the string in this case) - I can obviously easily get at the bound value, but it's the parent I need.
Thanks in advance for any help!
Phil

For people in the future who stumble on this question:
When .NET 4.5 becomes available it will have a number of new properties on the BindingExpression to greatly simplify what you are looking for.
ResolvedSource - The object that is actually being bound to, helpful when you have a binding source like 'grandparent.parent.me.Name'. This would return the 'me' object.
ResolvedSourcePropertyName - The name of the property on the ResolvedSource that is bound to. In the case above, "Name".
Similarly, there will be Target, and TargetName properties.
With these helper properties on BindingExpression you could use some shorter and much more simplified reflection that is more likely to work in extreme situations (indexers).

Below is a quick implementation for an extension method that will do just what you are looking for. I couldn't find anything related to this either. The method below will always return null if for some reason the value cannot be found. The method won't work when the path includes []. I hope this helps!
public static T GetValue<T>(this BindingExpression expression, object dataItem)
{
if (expression == null || dataItem == null)
{
return default(T);
}
string bindingPath = expression.ParentBinding.Path.Path;
string[] properties = bindingPath.Split('.');
object currentObject = dataItem;
Type currentType = null;
for (int i = 0; i < properties.Length; i++)
{
currentType = currentObject.GetType();
PropertyInfo property = currentType.GetProperty(properties[i]);
if (property == null)
{
currentObject = null;
break;
}
currentObject = property.GetValue(currentObject, null);
if (currentObject == null)
{
break;
}
}
return (T)currentObject;
}

Just for further information, PropertyPath resolution is handled by an internal MS class called PropertyPathWorker, which lives in PresentationFramework under MS.Internal.Data. If you open it up in Reflector, you can see it's quite complicated, so I wouldn't recommend trying to duplicate its functionality. It's tightly coupled with the overall Binding architecture.
The most robust way to support all property path syntax--including attached dependency properties and hierarchical traversal--is probably to create a dummy DependencyObject with a DependencyProperty. You can then create a Binding from your 'owner' path to the dummy dependency property, create a new BindingExpression and then call the expression's UpdateTarget. It's a rather heavy way of accomplishing what, on the surface, looks like a simple task, but I think there are a lot of hidden gotchas in the way property paths are resolved for binding.

I believe this other StackOverflow solution posted here may also work for you.
Copied code block for reference, read original post for more details provided by Thomas Levesque.
public static class PropertyPathHelper
{
public static object GetValue(object obj, string propertyPath)
{
Binding binding = new Binding(propertyPath);
binding.Mode = BindingMode.OneTime;
binding.Source = obj;
BindingOperations.SetBinding(_dummy, Dummy.ValueProperty, binding);
return _dummy.GetValue(Dummy.ValueProperty);
}
private static readonly Dummy _dummy = new Dummy();
private class Dummy : DependencyObject
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(Dummy), new UIPropertyMetadata(null));
}
}

As Dan Bryant already noted, PropertyPath resolution is tightly coupled with the overall binding architecture.
If you need the exact same resolution as WPF does it, you should probably use Thomas Levesque's answer to this question.
However, if you just need general path resolution, you can use a nuget package Pather.CSharp I developed that does exactly that.
It is essentially similar to zhech's answer, but more sophisticated.
Its main method is Resolve on the Resolver class. Passing in the target object and a path as string returns the desired result.
An Example:
IResolver resolver = new Resolver();
var target = new { Property1 = new { Property2 = "value" } };
object result = r.Resolve(target, "Property1.Property2");
It also supports collection access via index or dictionary access via key.
Example paths for these are:
"ArrayProperty[5]"
"DictionaryProperty[Key]"

Just in case others find it useful, below is a quick one liner extension method for .net4.5 (and beyond) to retrieve the bound value..
public static object GetValue(this BindingExpression bindingExpression)
{
return bindingExpression?.ResolvedSource.GetType().GetProperty(bindingExpression.ResolvedSourcePropertyName)?.GetValue(bindingExpression.ResolvedSource);
}

Related

property is trying to access the value from System.Windows.FrameworkElement

Anyone please try this scenario and share idea to resolve the issue which am facing.
Scenario:
In my class(inherited from Control) just am have declared the property FlowDirection which is the type of BulletGraphFlowDirection(Enum (Forward, Backward)).
I have used the new Keyword to FlowDirection to resolve warning which I get.
Warning 'DirectionSfBulletGraph1.MyClass.FlowDirection' hides inherited member 'System.Windows.FrameworkElement.FlowDirection'. Use the new keyword if hiding was intended.
public enum BulletGraphFlowDirection
{
Forward,
Backward
}
public class MyClass : Control
{
public new BulletGraphFlowDirection FlowDirection
{
get { return (BulletGraphFlowDirection)GetValue(FlowDirectionProperty); }
set { SetValue(FlowDirectionProperty, value); }
}
// Using a DependencyProperty as the backing store for FlowDirection. This enables animation, styling, binding, etc...
public new static readonly DependencyProperty FlowDirectionProperty =
DependencyProperty.Register("FlowDirection", typeof(BulletGraphFlowDirection), typeof(MyClass), new PropertyMetadata(BulletGraphFlowDirection.Backward));
}
Issue:
When I try to set the property FlowDirection value in Xaml page it’s just thrown the error message” Backward is not a valid value for FlowDirection ”.
<local:MyClass x:Name="myClass" FlowDirection="Backward"/>
My guess is the FlowDirection property is trying to access the value from System.Windows.FrameworkElement. FlowDirection'(Enum(LeftToRight,RightToLeft))
Am not getting error when the property value is set through code behind.
myClass.FlowDirection = BulletGraphFlowDirection.Backward;
Why am I getting issue when declared from Xaml page Its very difficult to found root cause of it. Please share me idea to resolve.
Regards,
Jeyasri M
Well, as you said, it tries to use the value of the original FlowDirection.
The reason is that hiding != overriding. So if your element is stored in a collection of type Control and FlowDirection is called on its elements then the Control.FlowDirection will be called and not the MyClass.FlowDirection. Probably your control is handled as Control and not MyClass when it tries to parse the xaml and initialize the view.
When you set the value in code-behind you specify it explicitly that you want to set the MyClass.FlowDirection by using a MyClass typed variable.
If you would instantiate this variable as:
Control myClass = new MyClass();
Then
myClass.FlowDirection = BulletGraphFlowDirection.Backward;
wouldn't work either in my opinion.
As Karmacon suggested, changing the name will solve the problem. (I guess that forces the xaml parser to handle your control as MyClass and not Control)

Can a PropertyChangedCallback be persuaded to run when the bound property's value is unchanged?

My application uses the MVVM architecture, with the ViewModel having no knowledge of the View. When a ViewModel object requires a new View be shown it exposes a public ShowNewView property that is an object whose class is based on my ViewModel base class. The WPF View binds a custom DependencyProperty to this and uses the PropertyChangedCallback to construct and show an approperiate Window.
This all works well the first time the ShowNewView property is set. However, if the user closes the window and then attempts to re-open it, the ShowNewView property's value has not changed when the PropertyChanged event is raised and the PropertyChangedCallback is not invoked.
In order to 'trick' the DependencyProperty into detecting that the value has changed (even though the value stored in the ViewModel's property may not have actually changed), I have used the SetCurrentValue method exposed by the Window class to force the DependencyProperty's value to null.
#region ShowNewViewProperty
private static readonly DependencyProperty _ShowNewViewProperty =
DependencyProperty.RegisterAttached
(
"ShowNewView",
typeof(IRootViewModel),
typeof(WpfViewWindow),
new PropertyMetadata(ShowNewViewPropertyChanged)
);
public static DependencyProperty ShowNewViewProperty { get { return _ShowNewViewProperty; } }
public static IRootViewModel GetShowNewView(Window source)
{
return (IRootViewModel)source.GetValue(ShowNewViewProperty);
}
public static void SetShowNewView(Window target, IRootViewModel value)
{
target.SetValue(ShowNewViewProperty, value);
}
private static void ShowNewViewPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WpfViewWindow window = d as WpfViewWindow;
IRootViewModel newValue = e.NewValue as IRootViewModel;
if ((null != window) && (null != newValue))
{
// Create a child WpfViewWindow. This method is part of my
// framework that uses ResourceDictionary entries, imported by MEF
// to locate the View class corresponding to the ViewModel parameter's
// class.
WpfViewWindow modelessWindow = window.CreateWpfViewWindow(newValue);
if (null != modelessWindow)
{
// Show the new WpfViewWindow.
modelessWindow.Show();
}
// Clear the current value so that the next PropertyChanged event
// is processed even if the underlying value has not actually changed.
window.SetCurrentValue(ShowNewViewProperty, null);
}
}
#endregion
Technically this works, as it results in the callback being run when the PropertyChanged event fires, regardless of whether the value has actually changed or not. However, it results in the callback being called (recursively) twice every time the ViewModel's property is updated: once in response to the ViewModel's event and once in response to the SetCurrentValue method being called.
There are a number of questions here relating to the PropertyChangedCallback not being called, or not being called more than once, in other situations.
PropertyChangedCallback on DependencyProperty Only Firing Once covers the situation where the property is a collection and the collection content changes, but the collection itself does not. However, my property is not a collection and everything is working exactly as documented.
WPF dependency property setter not firing when PropertyChanged is fired, but source value is not changed looks very promising, but the answer only suggests using the callback that I am already.
Is there a more elegant way to achieve this that does not result in the callback being run twice for each PropertyChanged event from the ViewModel? I.e. is there some way to get around the framework's check to verify that the old and new values are different?
Clarification
The View being created isn't necessarily always a WPF Window, for example, in my unit tests it is a mock, and later in the project it may be a sperate logging assembly. Nor are all of the ViewModel objects from the same assembly, it is known that additional functionality will be required in the future, but the specifics are currently undefined. The application allows the user to connect a device by way of a simple network. Initially the network is ModbusRTU over RS-485, however, the end customer may want to use CANOpen or Profinet or some other transport layer, and I have to provide a plug-in mechanism that allows the new functionality to be added without changing the existing code.
To be fair, there are several alternative mechanisms that I could use to achieve the same result (i.e. having the ViewModel request a new View be created), but I'm interested in knowing if there is a way to make a DependencyPropety 'forget' what it's previous value was.
The usual solution to this type of problem is for you to extract the code from your ShowNewViewPropertyChanged method and to put it into a different method:
private void SomeNewMethod(IRootViewModel newValue)
{
// Create a child WpfViewWindow. This method is part of my
// framework that uses ResourceDictionary entries, imported by MEF
// to locate the View class corresponding to the ViewModel parameter's
// class.
WpfViewWindow modelessWindow = CreateWpfViewWindow(newValue);
if (null != modelessWindow)
{
// Show the new WpfViewWindow.
modelessWindow.Show();
}
// Clear the current value so that the next PropertyChanged event
// is processed even if the underlying value has not actually changed.
SetCurrentValue(ShowNewViewProperty, null);
}
Now you can simply call that method from both the ShowNewViewPropertyChanged handler and from wherever else you desire:
private static void ShowNewViewPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WpfViewWindow window = d as WpfViewWindow;
IRootViewModel newValue = e.NewValue as IRootViewModel;
if ((null != window) && (null != newValue))
{
window.SomeNewMethod(newValue);
}
}

Windows 8 - Animating custom property in code-behind

Basically, I want to make bunch of Shapes and make them animated. So I came up with following custom class:
public class FunkyShape : DependencyObject
{
public double Animator
{
get { return (double)GetValue(AnimatorProperty); }
set { SetValue(AnimatorProperty, value); }
}
public static readonly DependencyProperty AnimatorProperty =
DependencyProperty.Register("Animator", typeof(double), typeof(FunkyShape),
new PropertyMetadata(0, new PropertyChangedCallback(Animator_Changed)));
private static void Animator_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
double delta = (double)e.NewValue - (double)e.OldValue;
((FunkyShape)d).ProcessDelta((double)e.NewValue, delta);
}
private void ProcessDelta(double val, double delta)
{
Holder.Width = val;
Holder.Height = val;
// Keep shape centered
HolderPosition.X = delta / 2;
HolderPosition.Y = delta / 2;
}
private Shape Holder;
public TranslateTransform HolderPosition
{
get { return (TranslateTransform)Holder.RenderTransform; }
}
public FunkyShape(Canvas playground, Shape shapeToInit)
{
Holder = shapeToInit;
Holder.Width = 10;
Holder.Height = 10;
Holder.Fill = new SolidColorBrush(Colors.Blue);
Holder.HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Center;
Holder.RenderTransform = new TranslateTransform()
{
X = 500,
Y = 500
};
Holder.RenderTransformOrigin = new Point(0.5, 0.5);
// init done
playground.Children.Add(Holder);
Animate();
}
public void Animate()
{
DoubleAnimation g1 = GrowAnimation();
Storyboard sb = new Storyboard();
Storyboard.SetTarget(g1, this);
// CAN'T FIND ANIMATOR PROPERTY
Storyboard.SetTargetProperty(g1, "Animator");
sb.Children.Add(g1);
sb.Begin(); // THROWS EXCEPTION
}
private static DoubleAnimation GrowAnimation()
{
DoubleAnimation growAnimation = new DoubleAnimation();
growAnimation.Duration = TimeSpan.FromMilliseconds(3000);
growAnimation.From = 0;
growAnimation.To = 100;
growAnimation.AutoReverse = true;
growAnimation.EnableDependentAnimation = true;
growAnimation.RepeatBehavior = new RepeatBehavior(5);
return growAnimation;
}
}
However, when I try making an instance of the class and adding it to the canvas, I get Exception - Storyboard.Being() throws it and tells me that it can't find Animator property.
So - what am I doing wrong?
EDIT: After 3 code changes - it is still not working; I get "Cannot resolve TargetProperty Animator on specified object" error. So if somebody knows the answer - please help out by modifying the code. Thanks!
EDIT: OK, after 24 hours of banging head against the wall there is some progress - if I add shape through XAML it animates, but if I add it through code behind (Canvas.Children.Add), it doesn't work. Let me see if I can figure out why.
OK,
I've found the workaround for what is obviously a bug within the framework (although I'm sure some MS employee will post response and say it's a feature/it-is-by-design). Several things need to be done:
Add default/parameter-less constructor
Change base class of FunkyShape to UserControl.
Open up XAML view of the Page class where you want to add shapes
Add one instance of FunkyShape as a child within the Canvas XAML (<tm:FunkyShape /> for example). IT WON'T WORK WITHOUT THIS.
Make an instance of FunkyShape in code-behind, add it to canvas, start animation and enjoy seeing it works
Switch to less buggy technology.
In Windows 8 you cannot animate custom properties without also setting the enabledependentanimation property to true. This is because non-deterministic animations are disabled by default.
Reference: http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.animation.pointanimation.enabledependentanimation.aspx
Yes, you must define this property as a dependency property, not just a regular CLR property. This involves quite a bit of simple boiler plate code. See thus blog post for a complete example:
http://timheuer.com/blog/archive/2012/03/07/creating-custom-controls-for-metro-style-apps.aspx
OK, I had this problem too, but I didn't want to include a public parameterless constructor in my class, so I found another way.
Basically, the issue is that WinRT is a native platform, and it can't do reflection on .NET code. That's why the build process for WinRT apps generates metadata about the types used in XAML (you can find the relevant code in obj/(Debug|Release)/XamlTypeInfo.g.cs).
If a type is never used in XAML, no metadata about this type is generated, which means (among other things) that you can't animate the properties of the type.
If you're writing a class library, you can just include a XAML resource dictionary and declare a dummy instance of the type; it will cause metadata to be generated. However, it requires that the type has a public parameterless constructor, which might not be desirable.
So there is another solution: provide the metadata yourself. There are a several interfaces to implement, and they have many members, so it can be quite tedious to do manually. Fortunately, you don't have to! Here's what you can do:
add a public parameterless constructor to the class (temporarily)
create a XAML ResourceDictionary and declare an instance of the class in it (as described above)
copy the XamlTypeInfo.g.cs file into your project (I renamed it to XamlTypeInfo.cs)
replace the call to the constructor with throw new NotImplementedException()
delete the ResourceDictionary file
remove the public parameterless constructor
And you're done, the animation now works properly.
The process is still quite tedious, so it would be nice to have a tool to do the work for us...
EDIT: much easier solution: apply the [Bindable] attribute to the class. It makes the metadata generator take the type into account even if it's not used in XAML. (ignore the fact that the doc says it's for C++ types; it works just fine on C# classes as well)

Two-way bind a "virtual" list of strings to a column

I have a list of Strings.
Well, conceptually. They are stored somewhere else, but I want provide an object which acts like a list (and provides any necessary events on top of that), with properties that I could bind to.
I want to establish a two-way binding over this data, to display it as a modifiable column in a DataGrid. I have the following problems with that:
I can't make a two-way binding because the binding needs a path (i.e. I can't have it look like {Binding} or {Binding Path=.} in the column, must be {Binding Path=someField"} to be made modifiable if I got this right, which sounds reasonable).
I don't exactly know how the proxy collection object should look like, in terms of interfaces (would IEnumerable + INotifyCollectionChanged sufficient?)
Is there any solution which doesn't involve creating one proxy object per every String in the collection? Could you suggest an efficient design?
To keep the discussion on the rails, let's assume I want to bind to something like this:
class Source {
public String getRow(int n);
public void setRow(int n, String s);
public int getCount();
public void addRow(int position, String s);
public void removeRow(int position);
}
That's not exactly my case, but when I know how to bind to this, I think I'll be able to handle any situation like this.
I'm OK with having to provide an adapter object on top of that Source, with any necessary interfaces and events, but I don't want to have one adapter object per row of data.
While making an adapter for the Source is relatively clear, then, unfortunatelly, the core of the second problem ('not wrapping every string in a miniobject') is a clash built into the .Net and WPF..
The first thing is that the WPF does provide you with many ways of registering 'on data modified' callbacks, but provides no way of registering callbacks that would provide a value. I mean, the "set" phase is only extendable, not interceptable, and the "get" - nothing at all. WPF will simply keep and return whatever data it has once cached.
The second thing is that in .Net the string is ... immutable.
Now, if ever you provide a string directly as a pathless binding or as a datacontext to any control, you are screwed in a dead end. The problem is, that WPF actually passes only the actual value of the binding, without the information of "where it came from". The underlying control will be simply given the string instance, and will have no sane way of modifying it as the string cannot change itself. You will not be even notified about such attempt, just like with read-only properties. What's more - if you ever manage to intercept such a modification attempt, and if you produce a proper new string, the WPF will never ask you again for the new value. To update the UI, you'd have to mannually, literally, force the WPF to re-ask you by for example changing the original binding so it points elsewhere (to the new value) or set the datacontext (to the new instance). It is doable with some VisualTree scanning, as every 'changed' callback gives you the DependencyObjects (Controls!), so yo ucan scan upwards/downwards and tamper with their properties.. Remember that option - I'll refer to this in a minute.
So, everything boils down to the fact that to get a normal 2-way binding you do not have to have a Path, you "just" have to have a mutable underlying data object. If you have immutable one - then you have to use a binding to a mutable property that holds the immutable value..
Having said that, you simply have to wrap the strings some how if you want to modify them.
The other question is, how to do that. There's a plenty of ways to do it. Of course, you can simply wrap them like Joe and Davio suggested (note to Joe: INotify would be needed there also), or you can try to do some XAML tricks with attached properties and/or behaviours and/or converters to do that for you. This is completely doable, see for example my other post - I've shown there how to "inject a virtual property" that pulled the data completely from elsewhere (one binding+converter performed the wrapping on the fly, second binding extracted the values from the attached-wrapper). This way you could create a "Contents" property on the string, and that property could simply return the string itself, and it'd be completely 2-way bindable with no exceptions.
But.. it would NOT work 2-way-ish.
Somewhere at the root of your binding/behaviour/conveter chain, there will be an immutable string. Once your smart autowrapping binding chain fires with 'on modified' callback you will be notified with pair of old/new values. You will be able to remap the values to new and old strings. If you implemented everything perfectly, the WPF will simply use the new value. If you tripped somewhere, then you will have to push the new value artificially back to the UI (see the options I'd asked you to remember). So, it's ok. No wrapper, old value was visible, it was changeable, you've got new value, the UI displays new value. How about storage?
Somewhere in the meantime you've been given a old/new value pair. If you analyze them, you'll get old/new strings. But how do you update the old immutable string? Can't do. Even if autowrapping worked, even if UI worked, even if editing seemed to work, you are now standing with the real task: you onmodified callback was invoked and you have to actually update that immutable string piece.
First, you need your Source. Is it static? Phew. What a luck! So surely it is instanced. In the on-modified callback we got only a old+new string.. how to get the Source instance? Options:
scan the VisualTree and search for it in the datacontexts and use whatever was found..
add some more attached properties and binding to bind a virtual "Source" property to every string and read that property from the new value
Well doable, but smells, but no other options.
Wait, there's more: not only the old/new value and an instance of Source are needed! You also need the ROW INDEX. D'oh! how to get that from the bound data? Again, options:
scan the VisualTree and search for it (blaargh)...
add some more attached properties and bindings to bind a virtual "RowIndex" property to every (blaaergh)...
At this point of time, while I see that all of this seems implementable and actually might be working properly, I really think that wrapping each string in a small
public class LocalItem // + INotifyPropertyChanged
{
public int Index { get; }
public Source Source { get; }
public string Content
{
get { Source...}
set { Source... }
}
}
will simply be more readable, elegant and .. SHORTER to implement. And less error-prone, as more details will be explicit instead of some WPF's binding+attached magic..
I find your approach a little weird.
DataGrids are usually used to display Rows. Rows consist of data that belongs together.
You could for instance easily map a row to a certain class. This means that the columns in your datagrid represent properties in your class.
What you're trying to do is the opposite, you're trying to get a relation between the column values instead of the row values.
Wouldn't it be easier to have a collection of your class which you can then bound the column to?
For instance
class MyClass : INotifyPropertyChanged
{
// Remember to actually implement INotifyPropertyChanged
string Column;
}
If you would have an ObservableCollection of MyClass you could bind the DataGrid to this collection. Whenever the property which I called "Column" changes, you could update your special list.
You can do this by hooking up some events. With the implementation of INotifyPropertyChanged, your columns will be updated if you update the "Column"-value directly.
I have this bit of code I use to bind a list of custom object to a DataContextMenu. You can alter it to use a list of strings and bind it to what you need
class SampleCode
{
class Team
{
private string _TeamName = "";
private int _TeamProperty1 = 0;
ObservableCollection<Territory> _Territories = new ObservableCollection<Territory>();
public Team(string tName)
{
this.TeamName = tName;
}
public ObservableCollection<Territory> Territories
{
get { return _Territories; }
set { _Territories = value; }
}
public string TeamName
{
get { return _TeamName; }
set { _TeamName = value; }
}
public int TeamProperty1
{
get { return _TeamProperty1; }
set { _TeamProperty1 = value; }
}
}
class Territory
{
private string _TerritoryName = "";
Team _AssociatedTeam = null;
public Territory(string tName, Team team)
{
this.TerritoryName = tName;
this.AssociatedTeam = team;
}
public Team AssociatedTeam
{
get { return _AssociatedTeam; }
set { _AssociatedTeam = value; }
}
public string TerritoryName
{
get { return _TerritoryName; }
set { _TerritoryName = value; }
}
public void Method1()
{
//Do Some Work
}
}
class MyApplication
{
ObservableCollection<Team> _Teams = new ObservableCollection<Team>();
ContextMenu _TeritorySwitcher = new ContextMenu();
public MyApplication()
{
}
public void AddTeam()
{
_Teams.Add(new Team("1"));
_Teams.Add(new Team("2"));
_Teams.Add(new Team("3"));
_Teams.Add(new Team("4"));
foreach (Team t in _Teams)
{
t.Territories.Add(new Territory("1", t));
t.Territories.Add(new Territory("2", t));
t.Territories.Add(new Territory("3", t));
}
SetContextMenu();
}
private void SetContextMenu()
{
HierarchicalDataTemplate _hdtTerritories = new HierarchicalDataTemplate();
_hdtTerritories.DataType = typeof(Territory);
HierarchicalDataTemplate _hdtTeams = new HierarchicalDataTemplate();
_hdtTeams.DataType = typeof(Team);
FrameworkElementFactory _TeamFactory = new FrameworkElementFactory(typeof(TreeViewItem));
_TeamFactory.Name = "txtTeamInfo";
_TeamFactory.SetBinding(TreeViewItem.HeaderProperty, new Binding("TeamProperty1"));
FrameworkElementFactory _TerritoryFactory = new FrameworkElementFactory(typeof(TreeViewItem));
_TerritoryFactory.Name = "txtTerritoryInfo";
_TerritoryFactory.SetBinding(TreeViewItem.HeaderProperty, new Binding("TerritoryProperty1"));
_hdtTeams.ItemsSource = new Binding("Territories");
_hdtTeams.VisualTree = _TeamFactory;
_hdtTerritories.VisualTree = _TerritoryFactory;
_hdtTeams.ItemTemplate = _hdtTerritories;
_TeritorySwitcher.ItemTemplate = _hdtTeams;
_TeritorySwitcher.ItemsSource = this._Teams;
}
}
}
Lazy solution
Derive from ObservableCollection<string> and let that collection be populated from the Source. In the derived class, register to collection change events and update the source accordingly. Bind the DataGrid column to the observable collection.
This should be pretty straightforward to write, but has a big drawback of duplicating all data in the collection.
More efficient solution
Create an adapter (as you suggested) and implement IList<string> and INotifyCollectionChanged. Let the list operations fall through directly to the source. Bind the DataGrid column to the adapter.
This approach would require some tedious boilerplate, but it's a thin layer between the WPF control and your Source.
This really depends on how you're implementing the UI. Bea Stollnitz did an excellent post of virtualizing the ItemsSource for the WPF DataGrid at http://bea.stollnitz.com/blog/?p=344 . With work I used this to edit as well as display data.
The easiest way is by placing the string in a wrapper class.
public class Wrapper
{
public string Content{get;set;}
}
Then you use the string via the wrapper class. This was the list items remain the same but the content changes.
The problem is when you do this without that then an old string is being deleted and a new one is created and the collection is confused.
Start with an ObservableCollection<string>. Then set the bindable control's ItemsSource to the ObservableCollection.

PropertyGrid alternatives

I love PropertyGrid, well, at least the concept behind it - use reflection and attributes to edit your objects without writing much UI code.
My excitement died out pretty quickly though, the default PropertyGrid shipping with WinForms flat-out sucks. Well, it's fine for editing simple objects and such, but that's as far as it goes.
It doesn't display appropriate UITypeEditors for dynamic properties which have type "Object".
As soon as your objects contain collections, you might be able to edit them with so called CollectionEditor. However, it won't fire PropertyValueChanged event. So once you need to add undo functionality, you're screwed.
And I still haven't found an elegant way to add validation for CollectionEditor.
It's also problematic to implement undo if you have multiple objects selected, because in that case PropertyValueChanged event args ChangedItem is null.
I soon found myself writing hacks to address those issues with less than agreeable results.
What would you do?
Is there an elegant solution to at least the first three issues?
Is there an alternative propertygrid? Preferably free & without PInvokes?
A lot of the PropertyGrid's elegance comes from its simplicity. Above all else, it's designed to play nice with Visual Studio, and i'd expect to see it used primarily in custom UITypeEditors and extensions, rather than in application code.
Presumably the objects you are attaching to the PropertyGrid are classes of your own design? I've found that, in order to make good use of the property grid, you have to heavily decorate your classes and members with attributes.
You may find some joy in writing your own subclasses of CollectionEditor (and other types of editors) and attaching them to class members using the [Editor] attribute - if you can attach this attribute to your dynamic properties, you can force the use of a particular editor.
The only way I can think of adding validation to CollectionEditor is to override the CreateCollectionForm() method, returning an instance of your own, custom subclass of CollectionEditor.CollectionForm. There's a chance you will be able to fire the change events from here.
Unfortunately all I can do is nod and agree with the assertion about implementing undo. You might have to resort to 'backing up' the affected objects via cloning or serialization in order to implement undo.
I've seen alternatives to the built-in property grid control, but they exist mainly to offer different visual styles.
If someone is interested - here is a workaround for the PropertyValueChanged problem that simulates a change by invoking the MemberwiseClone function of System.Object if the CollectionEditor's PropertyValueChanged had been fired ...
public class FixedCollectionEditor : CollectionEditor
{
bool modified;
public FixedCollectionEditor(Type type)
: base(type)
{ }
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
value = base.EditValue(context, provider, value);
if (value != null && modified)
{
value = value.GetType()
.GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic)
.Invoke(value, new object[] { });
}
modified = false;
return value;
}
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
foreach (Control table in collectionForm.Controls)
{
if (!(table is TableLayoutPanel)) { continue; }
foreach (Control c1 in table.Controls)
{
if (c1 is PropertyGrid)
{
PropertyGrid propertyGrid = (PropertyGrid)c1;
propertyGrid.PropertyValueChanged += new PropertyValueChangedEventHandler(GotModifiedHandler);
}
if (c1 is TableLayoutPanel)
{
foreach (Control c2 in c1.Controls)
{
if (!(c2 is Button)) { continue; }
Button button = (Button)c2;
if (button.Name == "addButton" || button.Name == "removeButton")
{
button.Click += new EventHandler(GotModifiedHandler);
if (button.ContextMenuStrip != null)
{
button.ContextMenuStrip.ItemClicked += new ToolStripItemClickedEventHandler(GotModifiedHandler);
}
}
}
}
}
}
return collectionForm;
}
void GotModifiedHandler(object sender, EventArgs e)
{
modified = true;
}
}
Visualhint sells a replacement for the property grid that may help. As I have never used it in a real project, I don't know how well it works.

Categories

Resources