In this case I am looking to use strings declared in a resource dictionary as part of a binding on a Text property. Binding just a single dynamic resource string is not a problem:
<TextBlock Text="{DynamicResource keyToMyString}" />
But you quickly run into problems if you need to use a StringFormat on a MultiBinding because you need to insert dynamic text or want to combine several strings. For example, if my MultiBinding looks like this:
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1} some more text">
<Binding Source="{x:Static Resources:Strings.string1}" />
<Binding Source="{x:Static Resources:Strings.string2}" />
</MultiBinding>
<TextBlock.Text>
I can inject string1 and string2 from the specified resource file into the bound text, no problems there. But I cannot find a way to use strings from a dynamic resource in the same way. (I'm using this method to inject company and product names into text from a merged resource dictionary).
With a TextBlock I can circumvent this issue by using several Run items for the TextBlock content (reference):
<TextBlock >
<Run Text="{DynamicResource CompanyName}" />
<Run Text="{DynamicResource ProductName}" />
<Run Text="{DynamicResource MajorVersion}" />
</TextBlock>
but this is of no help when needing to bind the dynamic resource to the Window Title property. Is there anyway to accomplish this with (creative, if necessary) use of the existing markup extensions (like x:Static, etc)? Or do we have to write our own markup extension to achieve this?
Dynamic resource references have some notable restrictions. At least one of the following must be true:
The property being set must be a property on a FrameworkElement or FrameworkContentElement. That property must be backed by a DependencyProperty.
The reference is for a value within a Style Setter.
The property being set must be a property on a Freezable that is provided as a value of either a FrameworkElement or FrameworkContentElement property, or a Setter value.
Source: XAML Resources, MSDN.
So, in case of using the Binding, all the statements are violated.
As was shown, the DynamicResourceExtension works just fine for an instance of the Run class because the Run class (at least) is derived from the FrameworkContentElement class.
Additional references
Resources section: Wha' Happened Part Two: More Property Changes in WPF.
WPF: Dependency Properties & Resources.
Related
I have a value converter I wrote that allows me to bind against a property, test that property against a given (hard-coded) value, and return a brush based on if the test was true or false. The converter inherits from DependencyObject and implements IValueConverter. It exposes two dependency properties called PositiveBrush and NegativeBrush.
I declare it in XAML like this:
<UserControl.Resources>
<xyz:CBrushConverter x:Key="BrushConverter"
PositiveBrush="{DynamicResource Glyph.Resource.Brush.LightGreen}"
NegativeBrush="{DynamicResource Glyph.Resource.Brush.DarkGray}" />
</UserControl.Resources>
I can then adjust the color of a given element like this:
<TextBlock Foreground="{Binding SomeProperty, ConverterParameter='SomeValue', Converter={StaticResource BrushConverter}}" />
So in this example (making the assumption that SomeProperty returns a string) if the bound property 'SomeProperty' matches 'SomeValue' the converter will return the PositiveBrush as the Foreground (otherwise it will return the NegativeBrush).
So far so good - There may be other ways to skin this cat; but this has served me well for a long time and I don't really want to rock the boat.
What I would like to do however is declare my Positive and Negative brushes as part of my binding expression. Right now, if I wanted to use Red/Green and Blue/Yellow color combinations, I would need to declare two BrushConverters. But if I could declare the Positive/Negative brushes as part of the binding expression, I could use the same converter.
In pseudo-code, something like this (obviously this doesn't work):
<Grid Foreground="{Binding SomeProperty, ConverterParameter='SomeValue', Converter={StaticResource BrushConverter, BrushConverter.PositiveBrush='Red', BrushConverter.NegativeBrush='Green'}}" />
I did find a similar question on stack, How can I set a dependency property on a static resource? but it didn't explicitly address my question.
So... my google-foo is weak - I wasn't able to come up with the right search terms to dissect the Xaml binding syntax and work this out on my own, if it is even possible.
As always, any help is appreciated!
This should work:
<TextBlock>
<TextBlock.Foreground>
<Binding Path="SomeProperty" ConverterParameter="SomeValue">
<Binding.Converter>
<xyz:CBrushConverter PositiveBrush="Red" NegativeBrush="Green"/>
</Binding.Converter>
</Binding>
</TextBlock.Foreground>
</TextBlock>
Note however that you don't use the converter as static resource here. You would create a new converter instance for each Binding.
But if I could declare the Positive/Negative brushes as part of the binding expression, I could use the same converter.
You can't really do this. Converter is just a property of the Binding class. You still need to create an instance of the converter and set the dependency properties of this particular instance. What if you have several bindings that uses the same converter instance with different values for the PositiveBrush and NegativeBrush properties simultaneously?
You could define a converter instance inline though:
<TextBlock>
<TextBlock.Foreground>
<Binding Path="SomeProperty" ConverterParameter="SomeValue">
<Binding.Converter>
<xyz:CBrushConverter PositiveBrush="Green" NegativeBrush="Red" />
</Binding.Converter>
</Binding>
</TextBlock.Foreground>
</TextBlock>
I have a grid column defined. The parent grid gets its items from an ObservableCollection of type ItemClass. ItemClass has two properties: String Foo, and bool IsEditAllowed.
This column is bound to property Foo. There's a control template for editing the cell. I'd like to bind the ItemClass.IsEditAllowed property to the IsEnabled property of the TextBox in the template.
The question is how to bind it. Can this be done? The XAML below gets me "Cannot find source for binding with reference" in the debug trace.
The grid will let me bind the ItemClass itself to the field via some "custom" event thingy, and I can then bind to any of its properties. That's fine, but it seems kludgy. But if it's the only way, it's the only way.
<dxg:GridColumn
Header="Foo Column"
FieldName="Foo">
<dxg:GridColumn.EditTemplate>
<ControlTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay}"
IsEnabled="{Binding Path=IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ItemClass}, AncestorLevel=1}}" />
</ControlTemplate>
</dxg:GridColumn.EditTemplate>
</dxg:GridColumn>
There are two potentially easier ways to set up this binding.
Name the grid. Then your binding could look something like this (assuming dxg:GridControl has a property named "Items" and that you have assigned an instance of your ItemClass to that property):
<TextBox IsEnabled="{Binding Path=Items.IsEditAllowed, ElementName=MyGridControl} />
Use relative binding, but look for the GridControl rather than something nominally internal to the way GridControl works (that is, GridControlContentPresenter). This gets you away from the implementation details of GridControl, which are perhaps more likely to change in ways that break your application than are properties on GridControl itself.
<TextBox IsEnabled="{Binding Path=Items.IsEditAllowed, RelativeSource={RelativeSource AncestorType={x:Type dxg:GridControl}}}" />
You may also want to read up on the Visual Tree and the Logical Tree in WPF/xaml. The "Ancestor" in relative bindings refers to ancestors in the visual tree, that is, things like parent containers, and not to super- or base classes (as you've discovered, I think).
Here's the answer[1]. FindAncestor finds ancestors in the runtime XAML tree, not in arbitrary C# objects. It cannot walk up to the ItemClass instance from the member we're bound to. But we do know that somebody above us in the XAML tree bound us to that member, and he was bound to the ItemClass instance itself. So whoever that is, we find him, and then we've got the ItemClass.
So let's add debug tracing to the binding, and we'll see what the XAML situation looks like at runtime. No doubt there are other and probably smarter ways to do that, but I happen to know this one without any research.
First add this to the namespaces at the top of the XAML file:
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
...and then to the binding itself, add this:
diag:PresentationTraceSources.TraceLevel=High
Like so:
<TextBox Text="{Binding Value, Mode=TwoWay}"
IsEnabled="{Binding Path=IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ItemClass}, AncestorLevel=1}, diag:PresentationTraceSources.TraceLevel=High}"
/>
At runtime, when the TextEdit's IsEnabled property tries to get a value from the binding, the binding walks up through the XAML tree looking for an ancestor of the specified type. It keeps looking until it finds one or runs out of tree, and if we put tracing on it, it traces the type of everything it finds the whole way up. We've told it to look for garbage that it'll never find, so it will give us a trace of the type of every ancestor back to the root of the tree, leaf first and root last. I get 75 lines of ancestors in this case.
I did that, and found a few likely candidates. I checked each one, and the winner turned out to be dgx:GridCellContentPresenter, which has a RowData property. RowData has a lot of properties, and RowData.Row is the row's instance of ItemClass. dxg:GridCellContentPresenter belongs to the DevExpress grid library we're using; in another vendor's grid class, there would presumably be some equivalent.
Here's the working binding:
<TextBox Text="{Binding Value, Mode=TwoWay}"
IsEnabled="{Binding Path=RowData.Row.IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dxg:GridCellContentPresenter}, AncestorLevel=1}}"
/>
If DevExpress, the vendor, rewrites their GridControl class, we'll be in trouble. But that was true anyhow.
...
[1] Better answer, though it's too DevExpress specific to be of any real interest: The DataContext of the TextBox itself turns out to be dxg:EditGridCellData, which has a RowData property just like GridCellContentPresenter does. I can just use IsEnabled="{Binding Path=RowData.Row.IsEditAllowed}".
However, what I really wanted to do all along was not to present a grid full of stupid disabled textboxes, but rather to enable editing on certain rows in the grid. And the DevExpress grid lets you do that through the ShowingEditor event.
XAML:
<dxg:GridControl Name="grdItems">
<dxg:GridControl.View>
<dxg:TableView
NavigationStyle="Cell"
AllowEditing="True"
ShowingEditor="grdItems_TableView_ShowingEditor"
/>
</dxg:GridControl.View>
<!-- ... Much XAML ... -->
</dxg:GridControl Name="grdItems">
.cs:
private void grdItems_TableView_ShowingEditor(object sender, ShowingEditorEventArgs e)
{
e.Cancel = !(e.Row as ItemClass).IsEditAllowed;
}
Using WPF with MVVM, my VM has an indexed property
IObject1 this[string key]
I need to bind a property of the view to a property of IObject1, and the key of the object1 that I need is the name of the control in the view. Essentially I need nested bindings
<TextBlock x:Name="Key1" Text="{Binding ["Key1"].DisplayText}
but, the name will very for different items so I need the nested binding
<TextBlock x:Name="Key1" Text="{Binding [{Binding Name, RelativeSource={RelativeSource Self}].DisplayText}
My actual case is more complicated than this, but if I can get this far I think that I can figure out the rest.
I'm using Blend, and I'd love a way to teach my designer to do this type of thing within Blend, but I'm happy to use code if I need to.
Am I overlooking something obvious? I can't figure out how to do this and I haven't stumbled upon the correct Google / Stack Overflow search term.
Thanks.
That's a weird solution lol, anyhose, you can solve it with MultiBinding & converters.
<TextBlock.Text>
<MultiBinding Converter={StaticResource combine}>
<Binding Path=Dictionary />
<Binding Path=Name />
</Multibinding>
I have a custom control. This has the ability to do something with several other Controls. I would like it to have an element NotifyControl where I can bind some other controls like NotifyControl="{Binding ElementName=controlA}". This is fine but I would like to write down n controls. So maybe a list in the element value or noting the element multiple times. Like
<MyControl NotifyControl="{Binding ElementName=a}" NotifiyControl="{Binding ElementName=b}" />
or
<MyControl NotifyControl="{Binding ElementName=a}, {Binding ElementName=b}" />
Which one is possible and how do to it? I got no luck with an array type, maybe my notation like above is wrong.
EDIT:
I now have
<MyControl>
<MyControl.NotifyControls>
<NotifyControlWrapper View="{Binding ElementName=details}" Test="entry one" />
<NotifyControlWrapper View="{Binding ElementName=gauge}" Test="e2" />
</MyControl.NotifyControls>
</MyControl>
<OtherControl x:Name="details" />
NotifyControls is a DependencyProperty and filled with two entries, so this part works fine. The source of NotifyControlWrapper is just a class derived from DependencyObject with the two dependency properties View (type INotifyControl) and Test (type String).
As I sayed my list gets two entries with two NotifyControlWrapper. But while Test contains the given String, View is null. Why is that or how to debug?
Neither one in your question is possible. You can't add the same property twice so #1 won't work. You can't add two bindings so #2 won't work. I would add a property NotifyControls as a List type. NotifyControl could still be available as a separate item or to add to the list of controls in NotifyControls. You can add items in Xaml:
<MyControl.NotifyControls>
<ControlWrapper Control="{Binding ElementName=a}"/>
<ControlWrapper Control="{Binding ElementName=b}"/>
</MyControl.NotifyControls>
ControlWrapper would just have a single member property, Control, so that you can specify the binding.
If N is fixed, you can use a MultiBinding (with a converter):
<MyControl>
<MyControl.NotifyControl>
<MultiBinding Converter="...">
<Binding ElementName="controlA" />
<Binding ElementName="controlB" />
<Binding ElementName="controlC" />
<Binding ElementName="controlD" />
<Binding ElementName="controlE" />
...
</MultiBinding>
</MyControl.NotifyControl>
</MyControl>
If N changes, an option would be to add an ObservableCollection<> to your class which you add/remove the controls to, and then bind to it (again, with a converter)
<MyControl NotifyControl="{Binding ElementName=ParentElement, Path=MyObservableCollection, Converter=...}" />
I am wondering what am I missing? The binding is not displaying at all in the textbox. These are my codes:
XAML Namespace:
xmlns:c="clr-namespace:mySystem.Workspace"
DataContext and Resources:
<Grid.Resources>
<c:Parameter x:Key="mySource"/>
</Grid.Resources>
<Canvas>
<Canvas.DataContext>
<Binding Source="{StaticResource mySource}" />
</Canvas.DataContext>
Textbox:
<TextBox x:Name="TextBox" Width="159" Height="26" Canvas.Left="36" Canvas.Top="47">
<TextBox.Text>
<Binding Path="JobKey" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
The class:
namespace mySystem.Workspace
{
public class Parameter : Object
{
The accessors:
public BasePar JobKey
{
get { return jobKey; }
set { jobKey = value; }
}
There are lots of odd things here but the most obvious one that will get you working is that the Binding Path is case sensitive.
Change your binding to:
<Binding Path="JobKey" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>
This should get the binding working.
I'm also not sure what type BasePar is, or is meant to be, but unless you are doing something clever intentionally, just make it a standard type like string.
You should also probably not use the namespace System.Workspace, but something related to your own project.
After your response, the only thing I can guess that the BasePar object is intended for, is to be used within a DataTemplate, on an ItemsControl say. DataTemplates have the behaviour that when they do not know how to render an Object they will fall back the the Object's .ToString() method.
Now, in my comment I said that I don't think the TextBox can have a DataTemplate, and I believe this is true however I did find a trick at this Stackoverflow question which templates a content control and a textblock instead. The code is below:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:System.Workspace"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<c:Parameter x:Key="mySource"/>
<DataTemplate x:Key="MyDataTemplate">
<TextBlock Text="{Binding}"/>
</DataTemplate>
</Grid.Resources>
<Canvas>
<Canvas.DataContext>
<Binding Source="{StaticResource mySource}" />
</Canvas.DataContext>
<ContentControl
Content="{Binding Path=JobKey}"
ContentTemplate="{StaticResource MyDataTemplate}" />
</Canvas>
</Grid>
I don't have time right now to get the TextBox working - don't even know if it is possible, given my first few tries. However, this might help get you where you need to go.
But still - if I was me I'd just use simple binding to standard objects. I can't see the benefit of the BasePar class in this scenario.
What does the BasePar implementation look like? Have a look in the Debug Output window to see if you have a line like this:
System.Windows.Data Error: 1 : Cannot create default converter to perform 'two-way' conversions between types 'WpfApplication1.BasePar' and 'System.String'. Consider using Converter property of Binding. BindingExpression:Path=JobKey; DataItem='Parameter' (HashCode=14209755); target element is 'TextBox' (Name='TextBox'); target property is 'Text' (type 'String')
This is telling you that you are trying to bind to the property, but WPF cannot create a 2-way binding, because it cannot convert the text (you type into the TextBox) into a 'BasePar' object.
As per David's suggestion, you could bind to a primitive string type, or alternately (as per the warning message above) you could add a Converter to the binding to convert a string into a BasePar.
you need to make jobkey a DependencyProperty by deriving it from DependencyObject or derive your class from INotifyPropertyChanged and add all the notify code, etc.
if you do not do this, then you will not receive update notifications and your bindings wont work as expected.
Path="jobKey"
You need to bind to the property not the field, i.e. make that upper-case. Also: To debug bindings check the Output-window in Visiual Studio.