WPF get all bindings pointing to a property in my model - c#

I have a scenario like this
<StackPanel DataContext="{Binding WorldConfig}">
<TextBox Text="{Binding Width}">
</StackPanel>
I want to get all bindings pointing to "Width" property of WorldConfig object
I wrote a function for this as
BindingUtil.GetAllBindings(WorldConfig, "Width")
but it searches through all windows to find a FrameworkElement whose DataContext equals to the first parameter and then searches for a BindingExpression whose ResolvedSourcePropertyName equals to the second parameter. And it is quite inefficient. I thought of caching the results but then any change to bindings in runtime will break it.
I'm looking for a more efficient way to do this.
I'm thinking that these BindingExpressions must be stored in somewhere I can query but I can't find it, only search results I'm getting for my searches are about how to do the opposite of this.

Related

How to bind resources properties in a DataTemplate

Hello all I have the following data template:
<DataTemplate DataType="Integer">
<StackPanel>
<xctk:IntegerUpDown Minimum="{Binding XPath=Min}"
Maximum="{Binding XPath=Max}"
Increment="{Binding XPath=Inc}"
ClipValueToMinMax="True"
AllowSpin="True">
<xctk:IntegerUpDown.Resources>
<converters:Parameter x:Key="IntegerParameter"
CurrentDevice="{Binding ElementName=Dock, Path=DataContext.CurrentDevice}"
ParameterName="{Binding RelativeSource={RelativeSource TemplatedParent}, XPath=#RegisterName}"
ParameterType="Integer">
</converters:Parameter>
</xctk:IntegerUpDown.Resources>
<xctk:IntegerUpDown.Value>
<Binding XPath="#Name"
Converter="{StaticResource IntegerDataConverter}"
ConverterParameter="{StaticResource IntegerParameter}"
Mode="TwoWay"/>
</xctk:IntegerUpDown.Value>
</xctk:IntegerUpDown>
</StackPanel>
</DataTemplate>
Aim of this data template is to call the converter to set the value of the IntegerUpDown control reading it from an external device.
Details on how to contact the device are stored in the converters:Parameter class (derived from dependency object). Two way means that the same mechanism should be used with ConvertBack to write the value to the device.
MultiBinding is not an option since the ConvertBack method could not be used to "create" all the parameters from a single integer value.
This template works almost as intended: I get an instance of the Parameter class for each item the data template is applied to.
Unfortunately the values for CurrentDevice (that should bind to the DataContext property CurrentDevice of the window named "Dock") and ParameterName (that should bind to an Xml attribute of the Xml Node the DataTemplate is applied to) are always null.
I'm sure the issue is in the way I try to bind the Parameter properties, but I'm not able to figure out the correct syntax. Any idea on how to get the result (I can't believe this can't be done... ).
Thanks for any help you can provide
As you may guess my idea was trying to find a workaround for the "you can't bind ConverterParameter" issue.
After further research on internet I found this post:
Bindable Converter Parameter
from this ntg123 guy. It actually solves my problem allowing to somehow bind the ConverterParameter to multiple sources using a syntax almost identical to the standard one.
It is based on a custom MarkupExtension and works really well in situations where Multibinding is not possible.

Binding to property of first item in related (detail) collection

Similar to WPF: How to bind to only one item in a collection, not using ItemsControl since I don't want to display all of them, except the collection is related to the main binding item. All the data come in via EntityFramework. As with the linked question, the xaml explains it best:
<StackPanel Grid.Row="1" Orientation="Horizontal" DataContext="{Binding CurrentCustomer}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Total Orders:" />
<TextBlock Text="{Binding Orders.Count}" />
</StackPanel >
<StackPanel Orientation="Horizontal">
<TextBlock Text="First Order:" />
<TextBlock Text="{Binding Orders.First.OrderDate}" />
</StackPanel >
</StackPanel>
As you can see, I guessed that since I could get a count of the related orders using 'Orders.Count', I tried to utilise the same linq syntax to try to retrieve Orders.First (I also tried Orders.FirstOrDefault), but this doesn't work.
Looking at the linked question, I tried Sheridan's '[]' syntax, but Orders[0].OrderDate give an output error of: System.Windows.Data Error: 40 : BindingExpression path error: '[]' property not found on 'object' ''HashSet'1' (HashCode=37425772)'. BindingExpression:Path=Orders[0].OrderDate; DataItem='Customer_<BigLongIdentityString>' (HashCode=21972018); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String').
Looking at the msdn article he linked to, I tried wrapping the expression in brackets (as per attached properties) and using slashes (as for when the source is a collection). Not unexpectedly, these failed too.
Can someone show me how I get to the property of the first item in a related collection?
If possible, you can always add a new property to your model that exposes the first item:
public Order FirstItem
{
get { return this.Orders.First(); }
}
Then, you can just bind to that new property.
Also, this answer is highly relevant. A hashset may not be a good fit, here. There's no indexer. I generally stick to ObservableCollections.
Also, with regard to EF generation compatible with databinding, take a look at this MSDN article. Read the section called
Updating code generation for data binding
You can mess with the T4 template and per the article,
Find and replace the first occurrence of “HashSet” with
“ObservableCollection”.
Happy WPF'ing.

How does binding elementname work exactly?

I remember reading a couple of weeks ago that it sometimes doesn't work inside templates, and I recently tried to bind things in two different windows and it couldn't find the name declarations, so I assumed that it was local to the namespace of the class and just bound by setting the datacontext instead. However, I'm really curious when I am able to use binding elementname and when I cannot, because it's far more convenient when it is possible.
edit: In reading that article, I found this to be interesting:
"For this reason, styles and templates both define their own XAML namescopes, independent of whatever location in an object tree where the style or template is applied."
if this is true, doesn't that mean that Binding ElementName should not work in templates at all? But then I definitely have some working bindings on ElementName within my templates. That is the most confusing part, why do some bindings randomly work inside the templates and others do not? It must have some method for trying to resolve the name even if it isn't in the template or same namescope
Basically you need to be in the same name scope (read this). Most UI elements are in the same tree sharing the same name scope, however there can be breaks and barriers (styles/templates) and if you have abstract objects like DataGrid columns they do not have a name scope at all.
I've been working with WPF long enough to guess when i'll run into problems and i know common areas but don't think there's an easy way to tell in all situations up-front.
if this is true, doesn't that mean that Binding ElementName should not work in templates at all? But then I definitely have some working bindings on ElementName within my templates.
Within is just fine, that is the same scope. The point here is that if you apply the template and they would not have their own scopes there would be conflicts.
e.g.
<Button/>
<Button/>
If we expand the ControlTemplate you would get something like:
<Border Name="bd" Background="{TemplateBinding Background}">...</Border>
<Border Name="bd" Background="{TemplateBinding Background}">...</Border>
Obviously we would get a name conflict.
Same for DataTemplates in ItemsControls, if you name controls in the template that name would conflict with the same control instance in the applied template of other items.
On another note, you can bind from inside a template to the outside because logically there can only be one instance with that name or you can give them a distinct precedence based on how "close" the name scope is, e.g.
<TextBox Name="tb" Text="Test"/>
<ItemsControl ItemsSource="ABC">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text, ElementName=tb}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

WPF Combobox binding: can't change selection

After wasting hours on this, following on the heels of my Last Problem, I'm starting to feel that Framework 4 is a master of subtle evil, or my PC is haunted.
I have three comboboxes and a textbox on a WPF form, and I have an out-of-the-box Subsonic 3 ActiveRecord DAL.
When I load this "edit record" form, the comboboxes fill correctly, they select the correct items, and the textbox has the correct text. I can change the TextBox text and save the record just fine, but the comboboxes CANNOT BE CHANGED. The lists drop down and highlight, but when you click on an item, the item selected stays the same.
Here's my XAML:
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Asset</TextBlock>
<ComboBox Name="cboAsset" Width="180"
DisplayMemberPath="AssetName"
SelectedValuePath="AssetID"
SelectedValue="{Binding AssetID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Status</TextBlock>
<ComboBox Name="cboStatus" Width="180"
DisplayMemberPath="JobStatusDesc" SelectedValuePath="JobStatusID"
SelectedValue="{Binding JobStatusID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Category</TextBlock>
<ComboBox Name="cboCategories" Width="180"
DisplayMemberPath="CategoryName"
SelectedValuePath="JobCategoryID"
SelectedValue="{Binding JobCategoryID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Reason</TextBlock>
<TextBox Name="txtReason" Width="380" Text="{Binding Reason}"/>
</StackPanel>
Here are the relevant snips of my code (intJobID is passed in):
SvcMgrDAL.Job oJob;
IQueryable<SvcMgrDAL.JobCategory> oCategories = SvcMgrDAL.JobCategory.All().OrderBy(x => x.CategoryName);
IQueryable<SvcMgrDAL.Asset> oAssets = SvcMgrDAL.Asset.All().OrderBy(x => x.AssetName);
IQueryable<SvcMgrDAL.JobStatus> oStatus = SvcMgrDAL.JobStatus.All();
cboCategories.ItemsSource = oCategories;
cboStatus.ItemsSource = oStatus;
cboAsset.ItemsSource = oAssets;
this.JobID = intJobID;
oJob = SvcMgrDAL.Job.SingleOrDefault(x => x.JobID == intJobID);
this.DataContext = oJob;
Things I've tried:
Explicitly setting IsReadOnly="false" and IsSynchronizedWithCurrentItem="True"
Changing the combobox ItemSources from IQueryables to Lists.
Building my own Job object (plain vanilla entity class using INotifyPropertyChanged).
Every binding mode for the comboboxes.
ItemsSource="{Binding}"
The Subsonic DAL doesn't implement INotifyPropertyChanged, but I don't see as it'd need to for simple binding like this. I just want to be able to pick something from the dropdown and save it.
Comparing it with my last problem (link at the top of this message), I seem to have something really wierd with data sources going on. Maybe it's a Subsonic thing?
EDIT: For some reason the set accessor is hit only on the AssetID property and only the first time. WPF is now heading for WTF :)
EDIT 2: You gotta be kidding me- I've removed the binding (ie it only has a displaymemberpath, a valuememberpath and an itemssouce) and it's STILL doing it! It accepts your first selection, and then won't change.
WPF Combo Boxes will not change the selected item if the currently selected item and the item that was just selected are considered equal by the object.Equals() method called on the newly selected object (i.e newlyslected.Equals(previoslySelected) ).
Overriding the Equals method on the class your binding the combobox items, should resolve the issue your are seeing.
I've narrowed it down to the Subsonic objects used as ComboBoxItems.
If you create a new class that uses exactly the same code as the relevant parts of the Subsonic one, it works.
If you use POCOs/datatables for the combos and Subsonic for the record being edited, it works.
But if you use Subsonic for both, it doesn't.
I had hoped to extend the subsonic objects and not have to code a full-blown BLL tier. Looks like I'm faced with doing that or throwing out Subsonic for the DAL. I might post a more specific question for the Subsonic folks.
Many thanks to all who contributed.
Old topic but I had the same problem and difficulty finding solution. This might help someone else.
Clue is above in WPF not detecting a different item has been seleted by user. (Symptom - event ComboBox_SelectionChanged only fires on first selection)
My scenario - lookup combo populated from IList built from a DISTINCT query. In this case the result of using NHibernate ICriteria.SetResultTransformer which only returns SOME fields, importantly NOT including the unique entity ID.
Solution - loop thru' IList after retrieval and give each entity a unique ID. WPF sees them as individuals and behaves appropriately.
Its only a value lookup - its the value content I was after.
The 'temporary' entities are never persisted. In this case it was a better approach than messing with overriding the object's Equals method for the sake of a simple GUI issue. An alternative would be to just copy or tranform the list into a format where WPF uses the value field to determine 'difference'...
Sounds like the field is somehow readonly, or that your change isn't being persisted. After the binding sets the new value, it will re-read the property to ensure that it was actually changed. If your property returns the old value, then it'll be re-selected in the combo box, giving the appearance that the value never changed.
I don't know that DAL, but can you step through the property setter code? You might also have an issue with type conversion.
EDIT reading your comment about the red rectangle -- it sounds as though your property (or something to do with the binding) is raising an exception. Unless, of course, you're using data validation in your UI. You might turn 'Break on all exceptions' in the debugger's settings, assuming you're using Visual Studio.
EDIT 2 You should check the VS Output pane for any error messages related to binding. You can also read this blog post which gives more info on debugging bindings.
It's hard to tell from a small sample of your code but try commenting out the line:
//this.DataContext = oJob;
and see if this helps.
Setting the DataContext and ItemsSource might be causing a conflict.
Did you write any global style for your combo box which may have a bug or something missing? Or are you using pure default styles for your combobox? Try removing any default styles applied.
Are you wiring up any events? If your code hooks up for event like PreviewMouseLeftButtonUp and marks event as handled then probably combobox may ignore and wont select anything.

WPF: Get Property that a control is Bound to in code behind

I am trying to find a way to get the Property to which a control is bound (in c#).
If I have the following:
<dxe:ComboBoxEdit DisplayMember="Name" ItemsSource="{Binding Path=NameOptions, Mode=OneTime}" SelectedItem="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" />
I am now trying to get the location to which the SelectedItem is bound to, i.e. the result should be "Name". Then in code I need to do some stuff with that ViewModel Property. Issue is that I can't just hard code this as it is a generic method that needs to work with each control on the form.
Thanks,
Richard
I think this should do it:
BindingExpression be = BindingOperations.GetBindingExpression((FrameworkElement)yourComboBox, ((DependencyProperty)Button.SelectedItemProperty));
string Name = be.ParentBinding.Path.Path;
To give credit where it's due.
Have a look into using BindingExpression
IE:
var bindingExpression = this.myComboBox.GetBindingExpression(ComboBox.SelectedItem);
string bindingPath = bindingExpression.ParentBinding.Path.Path
I see you're using a DXE ComboBox instead of a standard - expecting it derives from a normal .NET control object, you should still have this functionality.

Categories

Resources