I'm utilizing a data grid which has three columns of type DataGridTemplateColumn. These columns share almost identical behaviors and, as a consequence, utilize almost identical templates. The templates are copy-paste with a few resources changed out.
I would like to refactor the templates in to a generic version which uses an attached property to provide the necessary data. I've tried this but have been unable to access the property from inside the CellTemplate.
Methods I've tried are:
Bindings using RelativeSource: TemplatedParent.
Bindings using RelativeSource with various AncestorTypes.
Adding FrameworkPropertyMetadataOptions.Inherits to the FrameworkPropertyMetadata of the attached property.
The CellTemplate seems to have an odd degree of separation from its surroundings. What am I missing. If nothing, what is the appropriate solution to this problem?
A code example from you would have been helpful, but assuming that you have attached your property to the DataGrid, you should be able to bind to it from the CellTemplate using the following (untested):
<TextBlock Text="{Binding Path=(YourNamespace:YourClassName.YourAttachedPropertyName),
RelativeSource={RelativeSource AncestorType=DataGrid}, FallbackValue=''}" />
If you attached the property to the DataGridTemplateColumn, you would use:
<TextBlock Text="{Binding Path=(YourNamespace:YourClassName.YourAttachedPropertyName),
RelativeSource={RelativeSource AncestorType=DataGridTemplateColumn}, FallbackValue=''}" />
The FallbackValue property is not required, but it's a good practice to use it to avoid binding errors if the binding source can't be found.
Related
I'm trying to use the PropertyGrid component from PropertyTools to display information on an object. I can bind the object easily enough -- it's a property on my DataContext -- but one of the things that can't be derived from the object is the name that should be displayed in the tab header. (And I can't change that; the object I'm inspecting comes from a third party.) The proper name is a different property on my DataContext.
PropertyGrid has a way to change the way the tab header is displayed, by passing a DataTemplate to its TabHeaderTemplate property. But something bizarre happens inside of the template: my DataContext is gone, replaced by something else. When I try to say {Binding TabName} in the appropriate place inside the context, it errors out and tells me that TabName is not a valid property on class Tab. But my DataContext class isn't called Tab; that's something inside of PropertyTools's codebase!
I'm still new to WPF, so I have no clue what's going on here. Why is the in-scope DataContext that's perfectly valid in the rest of the XAML file being yoinked out from under me inside this template, and how can I fix it?
EDIT: Posting the XAML as requested. The template is literally just the simplest possible thing:
<UserControl.Resources>
<DataTemplate x:Key="HeaderTemplate">
<TextBlock Text="{Binding TabName}" />
</DataTemplate>
</UserControl.Resources>
And then further down the page,
<props:PropertyGrid
SelectedObject="{Binding Value}"
TabHeaderTemplate="{StaticResource HeaderTemplate}" />
But for some bizarre reason, in the template it's trying to interpret the binding inside the wrong DataContext!
In this case, just be sure to specify the source in your binding. There are a few ways to do this. One is to use the RelativeSource property of the Binding. Another is to use ElementName
Give your UserControl this attribute:
x:Name="Root".
Then change your binding to use it
<TextBlock Text="{Binding ElementName=Root, Path=DataContext.TabName}" />
Or use this:
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MyUserControl}}, Path=DataContext.TabName}"/>
Ok... this is leaving me scratching my head. I have two WPF controls--one's a user control and the other's a custom control. Let's call them UserFoo and CustomFoo. In the control template for CustomFoo, I use an instance of UserFoo which is a named part so I can get to it after the template is applied. That works fine.
Now both UserFoo and CustomFoo have a Text property defined on them (independently, i.e. not a shared DP using AddOwner. Don't ask...) that are both declared like this...
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(UserFoo), // The other is CustomFoo
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
null,
null,
true,
UpdateSourceTrigger.PropertyChanged
)
);
Notice specifically that the mode is set to TwoWay and the UpdateSourceTrigger is set to PropertyChanged, again for both.
So in the style template for CustomFoo, I want to bind CustomFoo's Text property as the source to the internal UserFoo's Text property. Normally, this is easy. You just set UserFoo's text property to "{TemplateBinding Text}" but for some reason it's only going one way (i.e. UserFoo is properly set from CustomFoo, but not the reverse), even though again, both DPs are set for two-way! However, when using a relative source binding instead of a template binding, it works great! Um... wha??
// This one works
Text="{Binding Text, RelativeSource={RelativeSource AncestorType={local:CustomFoo}}, Mode=TwoWay}"
// As does this too...
Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
// But not this one!
Text="{TemplateBinding Text}"
So what gives? What am I missing?
Found this forum post on MSDN: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0bb3858c-30d6-4c3d-93bd-35ad0bb36bb4/
It says this:
A TemplateBinding is an optimized form of a Binding for template scenarios, analogous to a Binding constructed with
{Binding RelativeSource={RelativeSource TemplatedParent}}
Note from OP: Contrary to what it says in the documentation, in actuality, it should be this...
{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}
I filed a complaint against the docs, and while they did add a sentence now stating they are always one-way, the code example still doesn't list the mode, but I guess it's better than nothing.)
The TemplateBinding transfers data from the templated parent to the property that is template bound. If you need to transfer data in the opposite direction or both ways, create a Binding with RelativeSource of TemplatedParent with the Mode property set to OneWayToSource or TwoWay.
More in: http://msdn.microsoft.com/en-us/library/ms742882.aspx
Looks like Mode=OneWay is one of the "Optimizations" of using a TemplateBinding
TemplateBinding does not support two-way binding, only Binding does that. Even with your BindsTwoWayBeDefault option, it won't support two-way binding.
More info can be found here, but to summarize:
However, a TemplateBinding can only
transfer data in one direction: from
the templated parent to the element
with the TemplateBinding. If you need
to transfer data in the opposite
direction or both ways, a Binding with
RelativeSource of TemplatedParent is
your only option. For example,
interaction with a TextBox or Slider
within a template will only change a
property on the templated parent if
you use a two-way Binding.
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.
I have this situation:
A IsToolbarButtonsEnabledProperty DependencyProperties
A have plenty of other DependencyProperties in a class (a huge class, needs to be this way)
A serie of Buttons on a toolbar.
The (IsEnabled) property of each of these buttons is a function of (IsToolbarButtonsEnabledProperty) throught a special converter, the buttons a differenced by ConvertParameter ("PreviousButton", "NextButton"...)
Opacity="{Binding IsEnabled, RelativeSource={RelativeSource Self}, Converter={StaticResource OpacityBoolToIntConverter}, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding Path=DPEnableLinks, Converter={StaticResource ToolButtonEnableConverter}, ConverterParameter='ZoomOut' }"
ToolButtonEnableConverter is a converter that compares ConverterParameter "PreviousButton" with other value of other dependency property (in class). I have to many DP to make one multivalueconverter, so I read them straight from my class ((MainWindow)App.Current.MainWindow;)
Questions
When I update other DPs the value isEnabled / Opacity, dont change. How to fix this?
Is there a general solution to make a Binding refresh everytime a DP changes.
(Repeating myself): I will be adding more and more DPs over time, so a MultiValueConverter seams odd.
One way to force the Binding to update is to create a (meaningless) property and add it to the Binding (using MultiBinding), and when you want to update your Binding you change that property, and all the Binding is updated.
I must add that the more "straightforward" way is to use MultiBinding to all the relevant properties. If you have way to many properties that you need to bind, maybe you should re-think if you can build this functionality some other way.
In my WPF application, I have a DataGrid which is bound to a collection in the viewmodel, but I want the width of the first column to be equal to a property on the viewmodel itself, like so:
public ObservableCollection<Booking> Bookings
{
return repository.Bookings();
}
public int MaxWidth
{
return 100;
}
(I realise there's no point in binding to a fixed property like this - it's for demo purposes)
<UserControl>
<DataGrid DataContext="{Binding Bookings}">
<DataGrid.Columns>
<DataGridTextColumn Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.MaxWidth}" Header="Provider" Binding="{Binding Name}" />
</DataGrid.Columns>
</DataGrid>
</UserControl>
But if I do this, the width of the column remains at the default value - i.e. just wide enough to accommodate its value.
What am I doing wrong?
EDIT: Attempted this, to see what happened:
<Label Name="tricky" Content="500" Visibility="Collapsed"></Label>
...
<DataGridTextColumn Width="{Binding ElementName=tricky, Path=Content}" Header="Provider" Binding="{Binding Name}" />
Which did nothing. Is it not possible to set the width dynamically?
I think the problem is the DataGridTextColum is not part of the Visual Tree (can verify with Snoop), so the binding never gets resolved.
The data obtained from the DataGridTextColumn is used to build the DataGridCell template, and at the time the template is built, the DataContext isn't set.
My suggestion would be to use a DataGridTemplateColumn, and specify your own CellTemplate that has the Width binding you need.
Alternatively a useful feature I use is as follow
IsEnabled="{Binding DataContext.IsEnabled,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
This allows you to link to properties outside of the itemssource of your grid.
MaxWidth and Bookings are siblings aren't they? So binding expressions for them should be identical.
Looking at your first XAML: Path=DataContext.MaxWidth - remove DataContext.
Looking at the second XAML: no need to specify Path twice, the first Path is enough, I'm not even sure if the actual construct is valid.
Generally, a lot of low level UI properties are POCOs rather than DPs, just make sure your target one is a DP.
Update - what you might want to do is to use FallbackValue = 900 as part of your binding expression, just to narrow the problem down - whether it's a binding problem or the target property isn't suitable for using as binding target.
Update 2 - most times you'll find yourself running out of steam rather quickly when going for that low level of managing your UI, it's often beneficial to create MVColum, managing headers, widths etc and create a behavior, which can apply all these settings in one go. Also that way you won't be dependent on a flavor of your target properties as you'll be setting them up right in your code.