I am trying to bind a property of a RibbonTabHeader to a property of its corresponding RibbonTab. However, it seems that the RibbonTab is not an ancestor of the RibbonTabHeader. I'm trying to bind on custom dependency properties, but for simplicity's sake we'll suppose this is what I want to do:
<Style x:Key="DynamicHeader" TargetType="r:RibbonTabHeader">
<Setter Property="Tooltip"
Value="{Binding Name,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type r:RibbonTab}}/>
Knowing that this equivalent produces the expected result, where the tooltip is "rbnTab1":
<Style x:Key="DynamicHeader" TargetType="r:RibbonTabHeader">
<Setter Property="Tooltip"
Value="{Binding Name,
ElementName=rbnTab1}/>
How could I recreate this behaviour directly in the style so that I can apply it to any header of any desired tab? Like so:
<r:RibbonTab Name="rbnTab2" Header="Tab 2" HeaderStyle="{StaticResource DynamicHeader}">
<r:RibbonTab Name="rbnTab3" Header="Tab 3" HeaderStyle="{StaticResource DynamicHeader}">
In order to provide closure to this topic, here is what I ended up doing:
In the end, I couldn't find a binding path to a property from the tab itself. Instead, rather than binding to a property from the tab, I define a custom style for the tab header which is BasedOn the original style, and I set the property for the header itself within that style. For good measure, my example also includes the custom dependency property I was using (although I don't think the issue is there because I use other custom dependency properties just fine):
MainWindow.xaml:
<Style x:Key="DynamicHeader" TargetType="r:RibbonTabHeader">
<Setter Property="BorderBrush" Value="{Binding Path=(ext:Tab.TabColor),
RelativeSource={RelativeSource Self},
Converter={core:StringToBrushConverter}}"/>
[...]
</Style>
[...]
<r:RibbonTab Name="rbnTab2" Header="Tab 2">
<r:RibbonTab.HeaderStyle>
<Style TargetType="RibbonTabHeader" BasedOn="{StaticResource DynamicHeader}">
<Setter Property="ext:Tab.TabColor" Value="CornflowerBlue"/>
</Style>
</r:RibbonTab.HeaderStyle>
</r:RibbonTab>
ControlExtensions.cs: (Custom Dependency Property)
public class Tab {
public static readonly DependencyProperty TabColorProperty =
DependencyProperty.RegisterAttached("TabColor", typeof(string), typeof(Tab), new
PropertyMetadata(default(string)));
public static void SetTabColor(UIElement element, string value) {
element.SetValue(TabColorProperty, value);
}
public static string GetTabColor(UIElement element)
{
return (string)element.GetValue(TabColorProperty);
}
}
Related
In a button, I am using a dependency property to pass information from the view model to the style of the button, so I can set the color of the color according to some conditions.
The code for the button is this:
The style in my xaml file:
<Style x:Key="BotonesColorEstadosTemplate" TargetType="{x:Type Button}">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="dp:BotonesEstadosAttachedProperty.CodigoEstado" Value="0"/>
<Condition Property="dp:BotonesEstadosAttachedProperty.AccionHabilitada" Value="true"/>
</MultiTrigger.Conditions>
</MultiTrigger>
<ContentTemplate>
</ContentTemplate>
</Style>
The dependency properties:
public static class BotonesEstadosAttachedProperty
{
public static readonly DependencyProperty CodigoEstadoProperty =
DependencyProperty.RegisterAttached(
"CodigoEstado",
typeof(short),
typeof(BotonesEstadosAttachedProperty));
public static short GetCodigoEstado(DependencyObject obj)
{
return (short)obj.GetValue(CodigoEstadoProperty);
}
public static void SetCodigoEstado(DependencyObject obj, short value)
{
obj.SetValue(CodigoEstadoProperty, value);
}
public static readonly DependencyProperty AccionHabilitadaProperty =
DependencyProperty.RegisterAttached(
"AccionHabilitada",
typeof(bool),
typeof(BotonesEstadosAttachedProperty));
public static bool GetAccionhabilitada(DependencyObject obj)
{
return (bool)obj.GetValue(AccionHabilitadaProperty);
}
public static void SetAccionHabilitada(DependencyObject obj, bool value)
{
obj.SetValue(AccionHabilitadaProperty, value);
}
}
How to use in the button:
<Button Name="btnAlmacenesActualizar" Content="..." Height="23" Margin="3,0,0,0" Width="23"
ap:BotonesEstadosDependencyProperty.CodigoEstado="{Binding CodigoEstadoActualizarAlmacenes}"
ap:BotonesEstadosDependencyProperty.AccionHabilitada="{Binding EsAccionActualizarAlmacenesHabilitada}">
With this, I can use a property of my view model and pass to my style, that use the information in the trigger to set the color of the button.
Now, I would like to have a style for the tooltip, to have the default configuration for all the tooltips, and I would like to can pass the text of the tooltip at first, but later I would like to pass another variables. By the moment, to test, I would like to try with the text.
I have this style:
<Style TargetType="ToolTip" x:Key="ToolTipDefaultStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="dp:ToolTipAttachedProperty.Texto"/>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
My StackPanel with the tooltip:
<StackPanel Name="spTiposIva" Orientation="Vertical" Margin="5,0,0,0"
ap:ToolTipDependencyProperty.Texto="{Binding TiposIvaTooltip}">
<StackPanel.ToolTip>
<ToolTip Style="{StaticResource ToolTipDefaultStyle}"/>
</StackPanel.ToolTip>
</StackPanel>
But in this case the text that is shown is "dp:ToolTipAttachedProperty.Texto". So I would like if it is possible to do the same than with the button, use a dependency propety to pass information from view model to the style.
Thanks.
You are currently not binding the attached property, you are assigning a string as Value. For binding to attached properties, you need to use the binding markup extension and parentheses, e.g.:
<Setter Property="Text" Value="{Binding (dp:ToolTipAttachedProperty.Texto)"/>
However, in your case you need to refer to the parent control of ToolTip, which your property is attached to. Normally, you would do this with a RelativeSource binding and AncestorType, but this does not work here, because ToolTip is not within the same visual tree as the parent control.
Instead, you can access the control via the PlacementTarget property on the parent ToolTip.
<Setter Property="Text" Value="{Binding PlacementTarget.(dp:ToolTipAttachedProperty.Texto), RelativeSource={RelativeSource AncestorType={x:Type ToolTip}}}"/>
Please also check your XAML for typos. The attached properties type does not match on the StackPanel and in the ToolTip style: ToolTipDependencyProperty or ToolTipAttachedProperty?
I'm trying to give a context menu items with different look and functionality, but I cannot find a way to bind commands to these items. Each menu item's view model is derived from one class AbstractEntryViewModel. Here is a shortened example of my current project's structure. Using ContextMenu.Resources is the only way I found to bind a template to a certain type.
<ContextMenu ItemsSource="{Binding Entries}">
<ContextMenu.Resources>
<DataTemplate DataType="{x:Type local:NopEntryViewModel}">
<!-- Content -->
</DataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:GroupEntryViewModel}"
ItemsSource="{Binding Entries}">
<!-- Content -->
</HierarchicalDataTemplate>
<!-- More templates -->
</ContextMenu.Resources>
</ContextMenu>
internal abstract AbstractEntryViewModel : INotifyPropertyChanged {
public abstract void Invoke ();
// ...
}
internal NopEntryViewModel : AbstractEntryViewModel {
public override void Invoke () {}
}
internal GroupEntryViewModel : AbstractEntryViewModel {
public override void Invoke () { /* ... */ }
// ...
}
// More view models
Normally I can bind a command to a MenuItem like so
<MenuItem Command="{Binding StaticResourceOrViewModelProperty}" />
How can I do the same thing with data templates? Is there an invisible container, a wrapper for the data template's content, that I can use to bind a command to?
For simplicity, suppose 2 derived ViewModels, VM1 and VM2, with respectively a Command Command1 and a Command2.
Two steps:
1) Define in the base ViewModel this property:
public Type Type
{
get { return GetType(); }
}
We cannot use GetType() directly, we need a wrapping property since WPF Bindings work only with properties.
2) Use this property to set a DataTrigger on the Style for the ContextMenu MenuItem:
<ContextMenu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="{x:Type local:VM1}">
<Setter Property="Command" Value="{Binding Command1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Type local:VM2}">
<Setter Property="Command" Value="{Binding Command2}"/>
</DataTrigger>
<!-- add other DataTriggers, one for every ViewModel -->
</Style.Triggers>
</Style>
</ContextMenu.Resources>
The DataTemplates are set as you do, with:
<DataTemplate DataType="{x:Type local:VM1}">
<!-- Content -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<!-- Content -->
</DataTemplate>
I want to create a parameterized style for a DataGridTextColumn using the trick from Thomas Levesque via attached properties. However, I can't make it work for my case.
Basically, I want to convert this
<DataGridTextColumn Header="Today Chg $" Binding="{Binding TodaysValueChange, StringFormat=N2}" IsReadOnly="True">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource RightAlignedCellStyle}">
<Setter Property="Foreground" Value="{Binding Path=TodaysValueChange, Converter={StaticResource PriceChangeToColor}}"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
into this
<DataGridTextColumn Header="Today Chg $" Binding="{Binding TodaysValueChange, StringFormat=N2}" IsReadOnly="True" CellStyle="{StaticResource ColoredCell}" ul:ThemeProperties.SignValue="{Binding TodaysValueChange}" ElementStyle="{StaticResource CellRightAlign}"/>
However, I got this error:
“A ‘Binding’ cannot be used within a ‘DataGridTextColumn’ collection. A binding can only be set on a DependencyProperty of DependencyObject.” for binding TodaysValueChange to ul:ThemeProperties.SignValue". I don’t know what it is complaining about.
Here is my ThemeProperties:
public static class ThemeProperties
{
public static double GetSignValue(DependencyObject obj)
{
return (double)obj.GetValue(SignValueProperty);
}
public static void SetSignValue(DependencyObject obj, double value)
{
obj.SetValue(SignValueProperty, value);
}
public static readonly DependencyProperty SignValueProperty = DependencyProperty.RegisterAttached("SignValue", typeof(double), typeof(ThemeProperties), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits));
}
this is my style resource in App.xaml:
<Style x:Key="ColoredCell" TargetType="{x:Type DataGridCell}" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Setter Property="Foreground" Value="{Binding Path=ul:ThemeProperties.SignValue, Converter={StaticResource PriceChangeToColor}}"/>
</Style>
I can't reproduce your exact problem (I'm not seeing the error you mention), but I can see several problems:
DataGrid columns are not part of the visual or logical tree, so they don't inherit the DataContext. Because of this, the binding for the SignValue property has nothing to bind to.
Even if the column inherited the DataContext, it still wouldn't work, because the column is "global" to the DataGrid, so it would get the DataGrid's DataContext, not the DataContext for a specific row.
When you set the binding in the ColoredCell style, it is relative to the current cell's DataContext, which is a data item and doesn't have the SignValue property set. You would need to make the binding relative to the cell itself (RelativeSource=Self). But because of the other problems above, it wouldn't work either...
Unfortunately I don't think there's an easy way my "parameterized style" trick work in this case, because the thing that needs to change in the style is the binding path, and there is no way to "bind the path of the binding". So I think you should stick to your initial solution (which is not that bad IMO).
So I have some code similar to the following: (Forgive any typos-- I tried to simplify in the SO editor for the post)
<my:CustomContentControl>
<my:CustomContentControl.Style>
<Style TargetType="{x:Type my:CustomContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentView}" Value="MyCustomView">
<Setter Property="Content">
<Setter.Value>
<my:CustomView DataContext="{Binding DataContextForMyCustomView"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</m:CustomContentControl.Style>
</my:CustomContentControl>
The problem is that whenever the DataTrigger occurs, the setter does set the Content property to my:CustomView, but it does not bind DataContext. If I move the same code outside of the trigger the DataContext binding works just fine.
Any ideas? If this is a limitation of some sorts, is there any work around?
Update:
I received the following error in the output window:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext. BindingExpression:Path=DataContextForMyCustomView; DataItem=null; target element is 'CustomView' (Name='customView'); target property is 'DataContext' (type 'Object')
The error you posted makes it sound like your custom control is in an object that doesn't have a DataContext, such as a DataGridColumn.Header.
To get around that, you can create a Freezeable object in your .Resources containing the binding you're looking for, then bind your my:CustomView.DataContext to that object
<my:CustomContentControl.Resources>
<local:BindingProxy x:Key="proxy"
Data="{Binding DataContextForMyCustomView, ElementName=MyControl}" />
</my:CustomContentControl.Resources>
...
<my:CustomView DataContext="{Binding Source={StaticResource proxy}}"/>
Here's the code for a sample Freezable object copied from here:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object),
typeof(BindingProxy), new UIPropertyMetadata(null));
}
Also, you really should use ContentTemplate instead of Content to avoid an exception if more than one object applies that style :)
I solved a similar problem by putting the UserControl into the resources and then changing the Content with that.
e.g. from my own code (different names, same concept)
<ContentControl Grid.Column="1"
Margin="7,0,7,0">
<ContentControl.Resources>
<mapping:Slide11x4MappingView x:Key="Slide11X4MappingView" DataContext="{Binding MappingViewModel}"/>
<mapping:MicrotubeMappingView x:Key="MicrotubeMappingView" DataContext="{Binding MappingViewModel}"/>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Acquirer.Sorter.TrayType}" Value="{x:Static mapping:TrayType.SLIDES11X4}">
<Setter Property="Content" Value="{StaticResource Slide11X4MappingView}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Acquirer.Sorter.TrayType}" Value="{x:Static mapping:TrayType.VIALS}">
<Setter Property="Content" Value="{StaticResource MicrotubeMappingView}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
I am trying to bind an ObservableCollection to a ContextMenu using MVVM. But when i try to fire the command nothing is happening. also, i need to pass the string as command parameter to the event.
Below is the xaml code:
<ContextMenu Name="ctxAddApplication" ItemsSource="{Binding Path=ApplicationTypes}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding AddRequirementCommand}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Below is the View Model Code:
public ObservableCollection<string> ApplicationTypes { get; private set; }
public ComposableCommand AddRequirementCommand { get; private set; }
this.AddRequirementCommand = new ComposableCommand(this.AddRequirementView);
private void AddRequirementView(object applicationName) {}
Please help !!!
Just in case you need the code:
<ContextMenu Name="ctxAddApplication" ItemsSource="{Binding Path=ApplicationTypes}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.AddRequirementCommand}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
The data context for each menu item will be whatever it is bound to. In your case, a string because your ApplicationTypes property is a collection of strings. Thus, your binding to set the command won't work because there is no AddRequirementCommand property on type String.
Inside ContextMenu view for each item is bound to the item from the collection.
<Setter Property="Command" Value="{Binding AddRequirementCommand}" />
this will try to locate 'AddRequirementCommand' in string class. Use RelativeSource in this Binding. Also use VS debugger and Output window to see binding errors, it helps a lot usually.