I have a ClockFace UserControl that exposes a number of properties to enable users to style it. The clock has two Ellipse objects as borders; an outer border and an inner border.
<Ellipse Name="OuterBorder" Panel.ZIndex="5" StrokeThickness="{Binding BorderOuterThickness}" Stroke="{Binding BorderOuteBrush}" />
<Ellipse Name="InnerBorder" Panel.ZIndex="6" StrokeThickness="{Binding BorderInnerThickness}" Margin="{Binding StrokeThickness, ElementName=OuterBorder}" Stroke="{Binding BorderInnerBrush}">
public static readonly DependencyProperty BorderInnerBrushProperty = DependencyProperty.Register("BorderInnerBrush", typeof(Brush), typeof(ClockFace), new
PropertyMetadata(new LinearGradientBrush(Color.FromRgb(118, 57, 57), Color.FromRgb(226, 185, 185), new Point(0.5, 0), new Point(0.5, 1))));
public Brush BorderInnerBrush
{
get { return (Brush)GetValue(BorderInnerBrushProperty); }
set { SetValue(BorderInnerBrushProperty, value); }
}
public static readonly DependencyProperty BorderOuterBrushProperty = DependencyProperty.Register("BorderOuterBrush", typeof(Brush), typeof(ClockFace), new
PropertyMetadata(new LinearGradientBrush(Color.FromRgb(226, 185, 185), Color.FromRgb(118, 57, 57), new Point(0.5, 0), new Point(0.5, 1))));
public Brush BorderOuterBrush
{
get { return (Brush)GetValue(BorderOuterBrushProperty); }
set { SetValue(BorderOuterBrushProperty, value); }
}
These can be set in a style and will update correctly when the styles are switched. I thought that I would be clever and add a shortcut property called BorderBrush that passes itself to the BorderOuterBrush property and then passes a copy of itself with its gradient reversed to the BorderInnerBrush property. To enable this code to run when the property was set after initialisation (by switching styles), I had to implement a PropertyChangedCallback method that calls the SetBorderBrushes method.
public new static readonly DependencyProperty BorderBrushProperty = DependencyProperty.Register("BorderBrush", typeof(Brush), typeof(ClockFace), new
PropertyMetadata(OnBorderBrushChanged));
private static void OnBorderBrushChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
((ClockFace)dependencyObject).SetBorderBrushes((Brush)e.NewValue);
}
public new Brush BorderBrush
{
get { return (Brush)GetValue(BorderBrushProperty); }
set { SetValue(BorderBrushProperty, value); }
}
private void SetBorderBrushes(Brush brush)
{
if (brush != null)
{
BorderOuterBrush = brush;
Brush innerBrush = BorderOuterBrush.Clone();
if (brush.GetType() == typeof(LinearGradientBrush) || brush.GetType() == typeof(RadialGradientBrush))
{
foreach (GradientStop gradientStop in ((GradientBrush)innerBrush).GradientStops)
{
gradientStop.Offset = 1 - gradientStop.Offset;
}
}
BorderInnerBrush = innerBrush;
}
}
When the BorderBrush property is set in a style, it all works just fine... that is until I switch the style at run time. Now the strangest thing happens... let me explain.
I have four preset styles and individually, they all work just fine. Three of them use the two BorderInnerBrush and BorderOuterBrush properties and one uses the shortcut BorderBrush property. I can switch between the styles using a ContextMenu and some code behind that accesses the xaml styles from Resources and sets them to the ClockFace object's Style property.
I can switch between the three styles that don't use the shortcut property endlessly without problem. I can also switch to the style that does use the shortcut property and it appears just fine. This is when the strangeness begins.
After switching to the style that uses the BorderBrush property, the BorderInnerBrush and BorderOuterBrush properties simply stop working. The preset Brush objects set in the various styles no longer get set on the two Ellipse objects. I pluged in some PropertyChangedCallback methods to the inner and outer border properties to see what was going on.
When I first run the application, I can switch to the three styles that don't use the shortcut property without problem. I put breakpoints on all three border Brush properties' PropertyChangedCallback methods and debugged the program. When switching to each of these three styles, the inner and outer border properties' callback methods' breakpoints were hit as you would expect. When switching to the style that uses the BorderBrush property, its callback method's breakpoint was hit. The SetBorderBrushes method sets the two other border Brush objects so the inner and outer border properties' callback methods' breakpoints were then hit as you would expect.
Again, this is the weird part. When switching to any of the other three styles after this, the inner and outer border properties' callback methods' breakpoints are no longer hit at all. Instead, the breakpoint in the callback method attached to the BorderBrush property is hit an the value of e.NewValue is null. As null values are ignored in the SetBorderBrushes method, no further breakpoints are hit.
After further investigation, I discovered that the e.NewValue was null because there was no default value set on the BorderBrushProperty DependencyProperty. Indeed, after adding a default Brush object to the declaration, this is the Brush that would be passed in e.NewValue in the callback method. Although the breakpoints in the two inner and outer border properties' callback methods would get hit after this, it was only because they are set in the SetBorderBrushes method. The Brush objects set in the BorderInnerBrush and BorderOuterBrush objects in the styles never get passed to these properties after the BorderBrush property has been used once.
One final point to note is that just as the default value of the BorderBrushProperty property is set when a value is not explicitly set in a style, the default values of the inner and outer border DependencyProperty objects are also set when their values are not explicitly set in a style, but only when the BorderBrush property is not also set in the style.
I've been stuck on this for days now and although an easy solution would be to remove the shortcut property, I'd rather find out what's going on and fix it. I hope I've provided enough information for some bright spark to be able to solve this problem, so, if you have any ideas or questions, please share them. Many thanks.
UPDATE >>
Following Rick's suggestion, I created another pair of Brush properties and set them from the callback methods attached to the other three Brush properties. Using this setup, I could switch between all of the styles without any visual problems... or so I thought.
I can now switch from the style that uses the BorderBrush (shortcut) property to the styles that set the two BorderInner and BorderOuter properties and the borders update correctly, so thanks Rick for getting me one step closer. I still have a problem when switching from the style that sets the BorderBrush property to the style that does not explicitly set any border Brush.
Therefore, I still have the same problem that the BorderInnerBrush and BorderOuterBrush properties don't work directly after being set from code behind. What's new is that if I then switch to a style that sets the two Brush properties, this seems to 'wake them up' again... if I then switch to the style that sets no border Brush, the inner and outer properties set their default values correctly again.
It's only after using a style that sets the two border properties from code behind that they become dead to being set in styles or using their default values. This is so strange... can anyone work it out?
UPDATE 2 >>>
First, thanks so much for your time and example Rick. After copying and pasting Rick's example code into a new project, I had to make a few alterations before it would run... maybe because I don't have Expression Blend 4? I installed the SDK and added the references to the noted dlls, but had to use the following XML namespace declarations instead:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
I also had to change each ChangePropertyAction declaration slightly to:
<ei:ChangePropertyAction TargetName="clock" PropertyName="Style" Value="{StaticResource styleBrush}"/>
Then, it worked fine for me and is a good representation of that portion of my actual ClockFace control, with two exceptions. The first is that my styles are switched from code behind via an event handler - this is clearly not causing my problem. The second difference is that I have set default Brush values on my versions of ActualOuterBrush and ActualInnerBrush, so that users of the control don't have to supply border brushes.
So my final problem is that the default inner and outer border brushes do not get set in a Style with no border properties explicitly set after the BorderBrush property has been set in a Style. Remember, the default values do get set up until this point. So, I experimented with Rick's example and added some default values:
private static readonly DependencyPropertyKey ActualInnerBrushPropertyKey = DependencyProperty.RegisterReadOnly("ActualInnerBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata(Brushes.Teal));
private static readonly DependencyPropertyKey ActualOuterBrushPropertyKey = DependencyProperty.RegisterReadOnly("ActualOuterBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata(Brushes.Salmon));
Now this project has a similar problem... the default values are never reset. I'm so baffled by this behaviour.
I've spent so long stuck on this problem now and although I have learned a number of useful things from Rick, I still have this problem with the default values. I've decided that the simplest thing to do is to remove the shortcut BorderBrush property. Rick has helped so much that I am awarding his answer as the correct answer, but if anyone reading this can help with the default value, I'd be grateful to hear from them.
Once a property has been set, styles no longer will be applied. By setting the inner and outer brush properties in your changed handler yourself, the dependency property subsystem is not aware that the the change was actually due to a style versus you doing it explicitly.
One solution is to expose protected read-only ActualInnerBorderBrush and ActualOuterBorderBrush properties and then have all three of the user-definable properties set these actual values in their respective change handlers. This way the user-visible properties can always be "as set by the user" without interfering with each other.
Edit:
Here is a complete working implementation of the five properties:
public class Clock : StackPanel
{
public Brush Brush
{
get { return (Brush)GetValue(BrushProperty); }
set { SetValue(BrushProperty, value); }
}
public static readonly DependencyProperty BrushProperty =
DependencyProperty.Register("Brush", typeof(Brush), typeof(Clock),
new UIPropertyMetadata((d, e) => (d as Clock).OnBrushChanged(d, e)));
public void OnBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ActualInnerBrush = e.NewValue as Brush;
ActualOuterBrush = e.NewValue as Brush;
}
public Brush InnerBrush
{
get { return (Brush)GetValue(InnerBrushProperty); }
set { SetValue(InnerBrushProperty, value); }
}
public static readonly DependencyProperty InnerBrushProperty =
DependencyProperty.Register("InnerBrush", typeof(Brush), typeof(Clock),
new UIPropertyMetadata((d, e) => (d as Clock).OnInnerBrushChanged(d, e)));
public void OnInnerBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ActualInnerBrush = e.NewValue as Brush;
}
public Brush OuterBrush
{
get { return (Brush)GetValue(OuterBrushProperty); }
set { SetValue(OuterBrushProperty, value); }
}
public static readonly DependencyProperty OuterBrushProperty =
DependencyProperty.Register("OuterBrush", typeof(Brush), typeof(Clock),
new UIPropertyMetadata((d, e) => (d as Clock).OnOuterBrushChanged(d, e)));
public void OnOuterBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ActualOuterBrush = e.NewValue as Brush;
}
public Brush ActualInnerBrush
{
get { return (Brush)GetValue(ActualInnerBrushProperty); }
private set { SetValue(ActualInnerBrushPropertyKey, value); }
}
private static readonly DependencyPropertyKey ActualInnerBrushPropertyKey =
DependencyProperty.RegisterReadOnly("ActualInnerBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata());
public static readonly DependencyProperty ActualInnerBrushProperty = ActualInnerBrushPropertyKey.DependencyProperty;
public Brush ActualOuterBrush
{
get { return (Brush)GetValue(ActualOuterBrushProperty); }
private set { SetValue(ActualOuterBrushPropertyKey, value); }
}
private static readonly DependencyPropertyKey ActualOuterBrushPropertyKey =
DependencyProperty.RegisterReadOnly("ActualOuterBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata());
public static readonly DependencyProperty ActualOuterBrushProperty = ActualOuterBrushPropertyKey.DependencyProperty;
}
and here is a little test program to prove that it works:
<Grid>
<Grid.Resources>
<Style x:Key="styleBrush" TargetType="local:Clock">
<Setter Property="Brush" Value="Red"/>
</Style>
<Style x:Key="styleInnerOnly" TargetType="local:Clock">
<Setter Property="InnerBrush" Value="Green"/>
</Style>
<Style x:Key="styleInnerOuter" TargetType="local:Clock">
<Setter Property="InnerBrush" Value="Blue"/>
<Setter Property="OuterBrush" Value="Yellow"/>
</Style>
<Style x:Key="styleEmpty" TargetType="local:Clock"/>
</Grid.Resources>
<StackPanel>
<local:Clock x:Name="clock" Orientation="Horizontal">
<Rectangle Width="100" Height="100" Fill="{Binding ActualInnerBrush, ElementName=clock}"/>
<Rectangle Width="100" Height="100" Fill="{Binding ActualOuterBrush, ElementName=clock}"/>
</local:Clock>
<StackPanel Orientation="Horizontal">
<Button Content="Default">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{x:Null}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Content="BrushOnly">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleBrush}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Content="InnerOnly">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleInnerOnly}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Content="InnerOuter">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleInnerOuter}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Content="Empty">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleEmpty}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</StackPanel>
</Grid>
This example uses behaviors. If you are not familiar with behaviors, install the Expression Blend 4 SDK and add these namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
and add System.Windows.Interactivity and Microsoft.Expression.Interactions to your project.
Related
I have a UserControl that contains a TabControl.
<UserControl x:Class="Test.MyUC"
....
xmlns:vm="clr-namespace:Test.ViewModels"
xmlns:ikriv="clr-namespace:IKriv.Windows.Controls.Behaviors"
...
<UserControl.Resources>
<vm:MyUCVM x:Key="VM" />
</UserControl.Resources>
<UserControl.DataContext>
<StaticResourceExtension ResourceKey="VM" />
</UserControl.DataContext>
<!-- Using Ivan Krivyakov's Attached Behavior -->
<TabControl ikriv:TabContent.IsCached="True"
TabStripPlacement="Top" ItemsSource="{Binding TabList}" IsSynchronizedWithCurrentItem="True">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:MyTab1VM}">
<v:MyTab1/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MyTab2VM}">
<v:MyTab2/>
</DataTemplate>
</TabControl.Resources>
...
Of course, in MyUCVM, I have TabList. Now, up to this point, everything works fine.
The problem starts when one of the tabs (e.g. MyTab1) in the TabControl needs to continuously and recursively read data from some external source (done in the ViewModel of course), and pass that data to View (via Binding) to display. Even up to this point everything is working. However, I do not want that to run when the tab is not visible, because there is no point to do that.
To do that, MyTab1VM needs to know if the associated View (MyTab1) is the selected tab. Therefore, I wired this up:
MyTab1:
<Style TargetType="TabItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}" />
</Style>
MyTab1VM
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected",
typeof(bool),
typeof(MyTab1VM),
new PropertyMetadata(false, new PropertyChangedCallback(IsSelectedChanged))
);
public bool IsSelected
{
get
{
return (bool) GetValue(IsSelectedProperty);
}
set
{
SetValue(IsSelectedProperty, value);
}
}
public static void IsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.Property == IsSelectedProperty)
{
MyTab1VM vm = d as MyTab1VM ;
vm.SetupToGetData();
}
}
private void SetupToGetData()
{
if (this.IsSelected)
{
System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += timer_Tick;
timer.Start();
}
}
private void timer_Tick(object sender, EventArgs e)
{
if (this.IsSelected)
this.MyData = ExternalSource.GetData();
else
{
(sender as System.Windows.Threading.DispatcherTimer).Stop();
}
}
Unfortunately, this setup only works when I set this.IsSelected = true; manually in the MyTab1VM's constructor. Leaving that out in the constructor, the data do not get shown in the view.
I have set breakpoints and confirmed that the binding for IsSelected is running correctly. Even the timer is running, and ExternalSource.GetData() is being called. But this.MyData = ExternalSource.GetData(); is not triggering the change from the ViewModel to the View.
The most puzzling part is that the same binding is triggered if IsSelected is set to true from the constructor.
Anyone out there knows what happened here?
I managed to do some fruitful troubleshooting on my own. I made a breakpoint in SetupToGetData() and I put this.GetHashCode() in my debugging watchlist. When I manually set this.IsSelected = true in the constructor, I realized that the SetupToGetData() method is called twice, with two different hash values. Planting another breakpoint in the constructor also showed that the constructor is called when I switch to this tab.
I have decided to move this to a new question, because it looks highly possible that the problem has nothing to do with binding.
Edit
Seems like I was right that this is the root of this problem. As that question is solved, so is this as well.
I am using visual studio dark theme. As a result when designing my views I cannot see the font if its black. A fix will be to set the background of the view to white. But our application has different themes so I cannot hard code that.
There are to great properties that I use when creating an usercontrol:
d:DesignWidth="1110" d:DesignHeight="400"
those properties are only affecting the view at design time. It will be great if I can create a property d:DesignBackground just so that I do not have to be adding and removing the background property every time I run the application.
Not sure if it's exactly what you're looking for, but what I do is just plop a trigger in the app.xaml to invoke using the IsInDesignMode property like;
Namespace (Thanks Tono Nam);
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=PresentationFramework"
XAML;
<Style TargetType="{x:Type UserControl}">
<Style.Triggers>
<Trigger Property="ComponentModel:DesignerProperties.IsInDesignMode"
Value="True">
<Setter Property="Background"
Value="#FFFFFF" />
</Trigger>
</Style.Triggers>
</Style>
Simple, but works, and sometimes I target other dependency properties like font and stuff too depending on the need. Hope this helps.
PS - You can target other TargetType's with their own properties the same way, like for example, ChildWindows, Popups, Windows, whatever...
You can create a static class with an attached property for design mode:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Helpers.Wpf
{
public static class DesignModeHelper
{
private static bool? inDesignMode;
public static readonly DependencyProperty BackgroundProperty = DependencyProperty
.RegisterAttached("Background", typeof (Brush), typeof (DesignModeHelper), new PropertyMetadata(BackgroundChanged));
private static bool InDesignMode
{
get
{
if (inDesignMode == null)
{
var prop = DesignerProperties.IsInDesignModeProperty;
inDesignMode = (bool) DependencyPropertyDescriptor
.FromProperty(prop, typeof (FrameworkElement))
.Metadata.DefaultValue;
if (!inDesignMode.GetValueOrDefault(false) && Process.GetCurrentProcess().ProcessName.StartsWith("devenv", StringComparison.Ordinal))
inDesignMode = true;
}
return inDesignMode.GetValueOrDefault(false);
}
}
public static Brush GetBackground(DependencyObject dependencyObject)
{
return (Brush) dependencyObject.GetValue(BackgroundProperty);
}
public static void SetBackground(DependencyObject dependencyObject, Brush value)
{
dependencyObject.SetValue(BackgroundProperty, value);
}
private static void BackgroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!InDesignMode)
return;
d.SetValue(Control.BackgroundProperty, e.NewValue);
}
}
}
And you can use it like this:
xmlns:wpf="clr-namespace:Helpers.Wpf;assembly=Helpers.Wpf"
<Grid Background="Black"
wpf:DesignModeHelper.Background="White">
<Button Content="Press me!"/>
</Grid>
You can use this approach to implement other property for design mode.
In 2022 you actually can and almost guessed it:
d:Background="White"
Note you can set pretty much any other control properties differently for design-time when prefix them with d:, for example:
<TextBlock Text="{Binding TextFromViewModel}"
Foreground="{StaticResource PrimaryBrush}"
d:Text="Design-time text"
d:Foreground="Black" />
Source: Use Design Time Data with the XAML Designer in Visual Studio
I have a 3rd party SplitButton control that exposes some DropDownContent and a boolean IsOpen dp to control whether the drop down content is shown or not.
In the case the DropDownContent is a StackPanel with several Buttons, each of which is bound to a command in the view model. In addition to executing that command, clicking the button needs to close the open DropDown content, which I am doing with the AttachedBehavior below.
But my binding, which simple needs to get a reference to the ancestor SplitButton control doesn't work. In the binding, you will note I am trying to Find the first Ancestor control of type SplitButton. I do see however that the debug info says ancestor level 1, so I changed the level to as high as 4, but still with an error.
Can someone see what the fix is?
binding error
System.Windows.Data Error: 4 : Cannot find source for binding with reference
'RelativeSource FindAncestor, AncestorType='Xceed.Wpf.Toolkit.SplitButton',
AncestorLevel='1''. BindingExpression:(no path); DataItem=null; target element is
'CloseDropDownContentBehavior' (HashCode=8896066); target property is 'DropDownButtonElement' (type 'SplitButton')
xaml
<DataTemplate x:Key="AddNewPartyTemplate">
<StackPanel HorizontalAlignment="Right" Margin="10">
<toolkit:SplitButton x:Name="theSplitButton" Content="{resx:Resx Subject_AddNewWithChoices}">
<toolkit:SplitButton.DropDownContent>
<StackPanel x:Name="theStackPanel">
<Button Content="{resx:Resx Person}" Command="{Binding AddNewPersonCommand}"
>
<i:Interaction.Behaviors>
<local:CloseDropDownContentBehavior
*** DropDownButtonElement="{Binding
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:SplitButton}}}"/>
</i:Interaction.Behaviors>
</Button>
...
</StackPanel>
</toolkit:SplitButton.DropDownContent>
</toolkit:SplitButton>
</StackPanel>
</DataTemplate>
attached behavior
public class CloseDropDownContentBehavior : Behavior<ButtonBase>
{
private ButtonBase _button;
protected override void OnAttached()
{
_button = AssociatedObject;
_button.Click += OnPartyButtonClick;
}
protected override void OnDetaching()
{
_button.Click -= OnPartyButtonClick;
}
// **** the point of it all
void OnPartyButtonClick(object sender, RoutedEventArgs e) { DropDownButtonElement.IsOpen = false; }
public static readonly DependencyProperty DropDownButtonElementProperty =
DependencyProperty.Register("DropDownButtonElement",
typeof(SplitButton), typeof(CloseDropDownContentBehavior), new UIPropertyMetadata(null, OnDropDownElementChanged));
public DropDownButton DropDownButtonElement
{
get { return (DropDownButton)GetValue(DropDownButtonElementProperty); }
set { SetValue(DropDownButtonElementProperty, value); }
}
private static void OnDropDownElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
}
}
Guessing it's because Interaction.Behaviors isn't part of the visual tree, so the binding won't find the ancestor. Have you tried simply:
DropDownElement="{Binding ElementName=theSplitButton}"
Update from comments: the solution in this case is to simply use x:Reference:
DropDownElement="{x:Reference theSplitButton}"
i dont know the SplitButton.DropDownContent but if its behave like a context menu the following answer might help: WPF context menu whose items are defined as data templates
this trick is to bind with RelativeSource Self or Type ContextMenu and then set the Path to PlacementTarget.DataContext.YourProperty
In WPF XAML there is the convenient DesignHeight and DesignWidth, for instance in code as
<UserControl ... d:DesignHeight="500" d:DesignWidth="500" ... />
which is great because I can build the layout with a representative, but not locked-in, control size.
However, I'm often building dark UIs, where labels and so forth need to be white, but my controls still need a transparent background color. This creates a design-time inconvenience because white seems to be the default background color for transparent controls in the designer, leading to unreadable white-on-white labels.
Is there a way or strategy for setting the design-time background color, with similar convenience as DesignHeight/DesignWidth?
There's an undocumented property d:DesignStyle of type Style that you can set on a user control. This style is only applied in the designer and is not used at runtime.
You use it like this:
<UserControl ... d:DesignStyle="{StaticResource MyDesignStyle}" />
Or like this:
<UserControl ...>
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<Setter Property="Background" Value="White" />
<Setter Property="Height" Value="500" />
<Setter Property="Width" Value="500" />
</Style>
</d:DesignerProperties.DesignStyle>
</UserControl>
The Background property is what you asked for. The Height and Width do replace your d:DesignHeight=500 and d:DesignWidth=500 in the <UserControl> tag. Then you have all your design properties at one place.
Note however, that any value set on the Style property (the one used at runtime) will also override the DesignStyle in the designer.
I found that you can do one for yourself. Custom design-time attributes in Silverlight and WPF designer is a tutorial how to do it for both Silverlight and WPF.
My answer was found here: Black Background for XAML Editor. There are a number of choices including checking System.ComponentModel.DesignerProperties.GetIsInDesignMode(this) at runtime.
This is the complete solution for DesignBackground:
public class DesignTimeProperties : DependencyObject
{
private static readonly Type OwnerType = typeof(DesignTimeProperties);
#region DesignBackground (attached property)
public static Brush GetDesignBackground(DependencyObject obj)
{
return (Brush)obj.GetValue(DesignBackgroundProperty);
}
public static void SetDesignBackground(DependencyObject obj, Brush value)
{
obj.SetValue(DesignBackgroundProperty, value);
}
public static readonly DependencyProperty DesignBackgroundProperty =
DependencyProperty.RegisterAttached(
"DesignBackground",
typeof (Brush),
OwnerType,
new FrameworkPropertyMetadata(Brushes.Transparent,
DesignBackgroundChangedCallback));
public static void DesignBackgroundChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (IsInDesignMode)
{
var control = d as Control;
var brush = e.NewValue as Brush;
if (control != null && brush != null)
{
control.Background = brush;
}
}
}
public static bool IsInDesignMode
{
get
{
return
((bool)
DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof (DependencyObject)).DefaultValue);
}
}
#endregion
}
Usage:
<UserControl ... infra:DesignTimeProperties.DesignBackground="Black" />
The d:DesignerProperties.DesignStyle technique shown on this page works great for applying a WPF design-time-only style to a single control, but it doesn't appear to work for a Style in a ResourceDictionary that would apply to all of the appropriately-typed controls or elements under the scope of the dictionary. Below is simple solution I found for deploying a designer-only style into a ResourceDictionary.
Consider for example a Window containing a TreeView, where we want the TreeViewItem nodes to show as fully expanded—but only at design time. First, put the desired style in the XAML dictionary in the normal way.
<Window.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
</Style>
</Window.Resources>
Here, the Style is put in the ResourceDictionary of the Window but of course you could use any other subsuming dictionary instead. Next, in the C# code, remove the style from the ResourceDictionary when design mode is not detected. Do this is in the OnInitialized override:
protected override void OnInitialized(EventArgs e)
{
if (DesignerProperties.GetIsInDesignMode(this) == false)
Resources.Remove(typeof(TreeViewItem));
base.OnInitialized(e);
}
Design Mode: Runtime Mode:
We are trying to get what the value of a DependencyProperty would be if it weren't locally set in the XAML. For instance, take this XAML...
<StackPanel Orientation="Vertical"
TextElement.FontSize="24">
<TextBlock Text="What size am I?"
FontSize="{Binding FontSize, RelativeSource={RelativeSource Self}, Converter={StaticResource PercentageConverter}, ConverterParameter=0.75}" />
</StackPanel>
This of course doesn't work. What I've done instead is this...
<TextBlock Text="What size am I?"
FontSize="{Binding (TextElement.FontSize), RelativeSource={RelativeSource AncestorType=FrameworkElement}, Converter={StaticResource PercentageConverter}, ConverterParameter=0.5}" />
...which checks the parent's value for the inherited FontSize property but that only works because FontSize does support inheritance. I'm looking for a solution for any DependencyProperty, not just ones that can be inherited.
Update
I changed the title of the question to be a little more generic as to what would be helpful to us. Specifically we'd love to be able to say 'For this specific instance of a DependencyObject, what would the value of a DependencyProperty be for each of the precedence levels?' When we query a DP, we of course get what the value after all precedence is applied. We'd love to say 'What would it be one or two levels higher?', etc.
A generic and robust solution should take into account the full rule set for DependencyProperty value precedence. I don't think that it is safe to say that if a value is not set locally, then its value would be the default value - the value instead could then be provided by a style, a trigger, etc.
I would suggest taking a look at the class DependencyPropertyHelper. This class contains a method called GetValueSource which when given a DependencyObject and a DependencyProperty will return a BaseValueSource result that tells you where the value for the property is coming from (style, style trigger, inherited, local, etc).
That being said, I think it would not be too hard to write an attached behaviour which you could use in XAML to return the "non-local" value for a specified DependencyProperty. This behaviour would check if the value source is local, and if it is clone the element, add it to the logical tree, and clear the local value. It would then read the value from the specified DependencyProperty on the clone, and set a read-only attached DependencyProperty to this value. This way you are letting the WPF property engine do the work for you.
Hope this helps!
Edit:
Here is a proof of concept for the attached behaviour. The behaviour has 2 properties:
PropertyToInspectValue - The DependencyProperty who's value you
wish to inspect.
InspectedValue - The computed non-local value for the specified DependencyProperty.
In XAML you bind to these 2 properties (setting one, reading the other):
<StackPanel>
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="30" />
</Style>
<Style x:Key="NamedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="35" />
</Style>
</StackPanel.Resources>
<!-- Without local value - uses default style (30)-->
<TextBlock l:DependencyPropertyValueHelper.PropertyToInspectValue="TextElement.FontSize"
Text="{Binding Path=(l:DependencyPropertyValueHelper.InspectedValue), RelativeSource={RelativeSource Self}}"
FontSize="15" />
<!-- Without local value - uses named style (35)-->
<TextBlock l:DependencyPropertyValueHelper.PropertyToInspectValue="TextElement.FontSize"
Style="{StaticResource NamedStyle}"
Text="{Binding Path=(l:DependencyPropertyValueHelper.InspectedValue), RelativeSource={RelativeSource Self}}"
FontSize="15" />
</StackPanel>
<StackPanel TextElement.FontSize="25">
<!-- Without local value - uses inherited value (25) -->
<TextBlock l:DependencyPropertyValueHelper.PropertyToInspectValue="TextElement.FontSize"
Text="{Binding Path=(l:DependencyPropertyValueHelper.InspectedValue), RelativeSource={RelativeSource Self}}"
FontSize="15" />
</StackPanel>
<!-- Without local value - uses default font size (11) -->
<TextBlock l:DependencyPropertyValueHelper.PropertyToInspectValue="TextElement.FontSize"
Text="{Binding Path=(l:DependencyPropertyValueHelper.InspectedValue), RelativeSource={RelativeSource Self}}"
FontSize="15" />
</StackPanel>
The example above will correctly show the values: 30, 35, 25, and 11. A couple of limitations in the code below is that the DependencyObject which we are inspecting must be a child of a Panel, and that currently only the Style property is copied over, but really all properties should be cloned since triggers in the style could use any property to change the value a DependencyProperty. Food for thought I guess ...
Attached Behaviour:
public static class DependencyPropertyValueHelper
{
private static bool _isUpdating;
#region InspectedValue Read-only Attached Dependency Property
public static object GetInspectedValue(DependencyObject d)
{
return d.GetValue(InspectedValueProperty);
}
private static readonly DependencyPropertyKey InspectedValuePropertyKey =
DependencyProperty.RegisterAttachedReadOnly("InspectedValue", typeof (object),
typeof (DependencyPropertyValueHelper),
new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty InspectedValueProperty = InspectedValuePropertyKey.DependencyProperty;
#endregion
#region PropertyToInspect Attached Dependency Property
public static void SetPropertyToInspectValue(DependencyObject d, DependencyProperty dependencyProperty)
{
d.SetValue(PropertyToInspectValueProperty, dependencyProperty);
}
public static DependencyProperty GetPropertyToInspectValue(DependencyObject d)
{
return (DependencyProperty)d.GetValue(PropertyToInspectValueProperty);
}
public static readonly DependencyProperty PropertyToInspectValueProperty =
DependencyProperty.RegisterAttached("PropertyToInspectValue", typeof (DependencyProperty),
typeof (DependencyPropertyValueHelper),
new FrameworkPropertyMetadata(OnPropertyToInspectValuePropertyChanged));
#endregion
#region Private ValueChanged Attached Dependency Property
public static readonly DependencyProperty ValueChangedProperty =
DependencyProperty.RegisterAttached("ValueChanged", typeof(object),
typeof(DependencyPropertyValueHelper),
new FrameworkPropertyMetadata(OnValuePropertyChanged));
#endregion
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!_isUpdating)
DetermineNonLocalValue(d, GetPropertyToInspectValue(d));
}
private static void OnPropertyToInspectValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dependencyProperty = (DependencyProperty) e.NewValue;
DetermineNonLocalValue(d, dependencyProperty);
var binding = new Binding
{
RelativeSource = new RelativeSource(RelativeSourceMode.Self),
Path = new PropertyPath(dependencyProperty.Name),
};
BindingOperations.SetBinding(d, ValueChangedProperty, binding);
}
private static void DetermineNonLocalValue(DependencyObject d, DependencyProperty dependencyProperty)
{
var element = d as FrameworkElement;
if (element == null)
return;
var valueSource = DependencyPropertyHelper.GetValueSource(element, dependencyProperty);
if (valueSource.BaseValueSource == BaseValueSource.Local)
{
_isUpdating = true;
var clonedDependencyObject = Activator.CreateInstance(element.GetType()) as FrameworkElement;
var parent = VisualTreeHelper.GetParent(element) as Panel;
// Currently only works if parent is a panel
if (parent != null)
{
// Copy any property which could impact the DP's value ...
// Probably check if this is a control and copy the ControlTemplate too ...
if (element.Style != null)
clonedDependencyObject.Style = element.Style;
parent.Children.Add(clonedDependencyObject);
// Let WPF provide us with the non-local value
element.SetValue(InspectedValuePropertyKey, clonedDependencyObject.GetValue(dependencyProperty));
parent.Children.Remove(clonedDependencyObject);
}
_isUpdating = false;
}
else
{
element.SetValue(InspectedValuePropertyKey, d.GetValue(dependencyProperty));
}
}
}
Edit 2
To elaborate on this approach. There is no chain of values exposed by WPF which we can inspect (or at least that I know of). The code above attempts to discover what the value would be if it was not set locally (from whatever value source: inherited, styles, triggers, etc). When it discovers this value it then populates the InspectedValue property with the result, which then can be read from.
A first attempt at this would be to:
Clear the local value for the DP, store it away somewhere (e.g. using DependencyObject.ClearValue)
Query the DP for its new value which we will assume is what the value would have been if it was not set locally. Record this value.
Restore the original value from step 1.
This approach fails - in that during step 2 your property will not pick up values from default styles (for example) since the style has already been applied, and simply removing a local value from a random DependencyPropery does not trigger a re-application.
Another approach would be in step 2 above - remove the element from the logical tree, and then add it back (since when elements are added to the tree, WPF goes through all possible value sources to determine the value of each DP). This too fails for the same reason - you are not guaranteed that styles will be re-applied.
The attached behaviour above tries a third approach - clone an element but make sure the the property in question is not set on the clone, and then add it to the logical tree. At this point WPF will then process the element as it would have with the original element and apply a value to the DP (inherited, triggers, style, etc). We can then read its value, and store it away. As demonstrated, this approach works for common use-cases, but is not perfect since if the non-local value was coming from a Trigger using a read-only property like IsMouseOver it would not pick it up (and a deep copy of the original object state would not fix this).
you should use the overloaded method of GetPropertyMetaData that suit best your need (that should be the one using types), and then you can retrieve the default value with... the DefaultValue property.