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:
Related
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 want my TransitionFrame's backgound to be white, other then black, as is by default.
I have added the following style declaration on the PhoneApplicationPage:
<Style TargetType="toolkit:TransitionFrame">
<Setter Property="Background" Value="White"/>
</Style>
How do I force the transition frame to use my style, that is defined in each page separately?
In App.xaml.cs, in InitializePhoneApplication:
RootFrame = new TransitionFrame();
RootFrame.Background = new SolidColorBrush(Colors.White);
In the same way, if you have more properties, and want to use a style, you could probably use
RootFrame = new TransitionFrame();
RootFrame.Style = (Style) Current.Resources["MyStyle"];
Edit: Might've misread the question, if you want a custom style per page (I don't judge :p) then you can access the rootframe by adding static to your RootFrame declaration:
public static TransitionFrame RootFrame { get; private set; }
and elsewhere, simply using App.RootFrame.<property>
I am trying to access commands that are defined in MainWindow.xaml in another window. I am only able to get grayed out titles of these commands. I am wondering what should be should be done in order to get a full access.
Sample of the command:
public partial class MainWindow : Window
{
public static RoutedUICommand AddCommand1 = new RoutedUICommand("Command ", "command1", typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(AddCommand1, AddCommand1Executed));
}
private void AddCommand1Executed(object sender, ExecutedRoutedEventArgs e)
{
AddNewItem picker = new AddNewItem();
picker.ShowDialog();
}
I access these command in style through databinding:
<Menu x:Name="TaskMenuContainer"><MenuItem x:Name="menuItem" Header="TASKS" ItemsSource="{Binding}" Template="{DynamicResource TaskMenuTopLevelHeaderTemplateKey}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding}" />
<Setter Property="Header" Value="{Binding Text, RelativeSource={RelativeSource Self}}" />
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</Style>
</MenuItem.ItemContainerStyle>
These commands work in pages that is loaded inside MainWindow.xaml through frame. However, if I have pop up window that is not part of MainWindow.xaml these commands are only grayed out and not functional anymore (cannot be executed). Any advice is highly appreciated!
The way you define the command, you define it for a particular window. If you want to handle the command globally, at the application level, you can use CommandManager.RegisterClassCommandBinding:
First, define you command in a separate static class:
public static class GlobalCommands
{
public static RoutedUICommand AddCommand1 = new RoutedUICommand("Command ", "command1", typeof(MainWindow));
}
Then, in you window or whatever place you want to put the command logic, register the command handlers:
public partial class MainWindow : Window
{
static MainWindow()
{
CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(GlobalCommands.AddCommand1, AddCommand1Executed, CanAddExecute));
}
private static void AddCommand1Executed(object sender, ExecutedRoutedEventArgs e)
{
AddNewItem picker = new AddNewItem();
picker.ShowDialog();
}
}
And in your menu style you should change the binding to x:Static:
<Setter Property="Command" Value="{x:Static my:GlobalCommands.AddCommand1}" />
When the command is routed, when checking for command bindings in each active element in the UI, the bindings registered for each element's class will also be checked. By registering the binding here, you can cause every instance of a class to be able to handle the specific command.
So, in the above example, the type Window is used and this will cause the routing to find the command binding in any instance of Window, once the routing reaches that instance in its search for a command binding.
You could instead, for example, restrict the handling to a specific subclass of Window, so that the command will only be bound in an instance of that subclass. Or you can use some other UI element type, so that that the presence of that specific type of element will cause the event to be handled. Just set the owning type for the registered command binding appropriately for your specific needs.
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.
this is just a question to discuss - what is the best way to make a view/edit control in WPF? E.g. we have an entity object Person, that has some props (name, surname, address, phone etc.). One presentation of the control would be a read-only view. And the other would have the edit view for this same person. Example:
<UserControl x:Name="MyPersonEditor">
<Grid>
<Grid x:Name="ViewGrid" Visibility="Visible">
<TextBlock Text="Name:"/>
<TextBlock Text="{Binding Person.Name}"/>
<Button Content="Edit" Click="ButtonEditStart_Click"/>
</Grid>
<Grid x:Name="EditGrid" Visibility="Collapsed">
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Person.Name}"/>
<Button Content="Save" Click="ButtonEditEnd_Click"/>
</Grid>
</Grid>
</UserControl>
I hope that the idea is clear. The two options I see right now
two grids with visibility switching and
a TabControl without its header panel
This is just a discussion question - not much trouble with it, yet I am just wondering if there are any other possibilities and elegant solutions to this.
Automatic Lock class
I wrote an "AutomaticLock" class that has an inherited attached "DoLock" property.
Setting the "DoLock" property to true re-templates all TextBoxes ComboBoxes, CheckBoxes, etc to be TextBlocks, non-editable CheckBoxes,etc. My code is set up so that other attached property can specify arbitrary template to use in locked ("view") mode, controls that should never automatically lock, etc.
Thus the same view can easily be used for both editing and viewing. Setting a single property changes it back and forth, and it is completely customizable because any control in the view can trigger on the "DoLock" property to change its appearance or behavior in arbitrary ways.
Implementation code
Here is the code:
public class AutomaticLock : DependencyObject
{
Control _target;
ControlTemplate _originalTemplate;
// AutomaticLock.Enabled: Set true on individual controls to enable locking functionality on that control
public static bool GetEnabled(DependencyObject obj) { return (bool)obj.GetValue(EnabledProperty); }
public static void SetEnabled(DependencyObject obj, bool value) { obj.SetValue(EnabledProperty, value); }
public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(AutomaticLock), new FrameworkPropertyMetadata
{
PropertyChangedCallback = OnLockingStateChanged,
});
// AutomaticLock.LockTemplate: Set to a custom ControlTemplate to be used when control is locked
public static ControlTemplate GetLockTemplate(DependencyObject obj) { return (ControlTemplate)obj.GetValue(LockTemplateProperty); }
public static void SetLockTemplate(DependencyObject obj, ControlTemplate value) { obj.SetValue(LockTemplateProperty, value); }
public static readonly DependencyProperty LockTemplateProperty = DependencyProperty.RegisterAttached("LockTemplate", typeof(ControlTemplate), typeof(AutomaticLock), new FrameworkPropertyMetadata
{
PropertyChangedCallback = OnLockingStateChanged,
});
// AutomaticLock.DoLock: Set on container to cause all children with AutomaticLock.Enabled to lock
public static bool GetDoLock(DependencyObject obj) { return (bool)obj.GetValue(DoLockProperty); }
public static void SetDoLock(DependencyObject obj, bool value) { obj.SetValue(DoLockProperty, value); }
public static readonly DependencyProperty DoLockProperty = DependencyProperty.RegisterAttached("DoLock", typeof(bool), typeof(ControlTemplate), new FrameworkPropertyMetadata
{
Inherits = true,
PropertyChangedCallback = OnLockingStateChanged,
});
// CurrentLock: Used internally to maintain lock state
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public static AutomaticLock GetCurrentLock(DependencyObject obj) { return (AutomaticLock)obj.GetValue(CurrentLockProperty); }
public static void SetCurrentLock(DependencyObject obj, AutomaticLock value) { obj.SetValue(CurrentLockProperty, value); }
public static readonly DependencyProperty CurrentLockProperty = DependencyProperty.RegisterAttached("CurrentLock", typeof(AutomaticLock), typeof(AutomaticLock));
static void OnLockingStateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
AutomaticLock current = GetCurrentLock(obj);
bool shouldLock = GetDoLock(obj) && (GetEnabled(obj) || GetLockTemplate(obj)!=null);
if(shouldLock && current==null)
{
if(!(obj is Control)) throw new InvalidOperationException("AutomaticLock can only be used on objects derived from Control");
new AutomaticLock((Control)obj).Attach();
}
else if(!shouldLock && current!=null)
current.Detach();
}
AutomaticLock(Control target)
{
_target = target;
}
void Attach()
{
_originalTemplate = _target.Template;
_target.Template = GetLockTemplate(_target) ?? SelectDefaultLockTemplate();
SetCurrentLock(_target, this);
}
void Detach()
{
_target.Template = _originalTemplate;
_originalTemplate = null;
SetCurrentLock(_target, null);
}
ControlTemplate SelectDefaultLockTemplate()
{
for(Type type = _target.GetType(); type!=typeof(object); type = type.BaseType)
{
ControlTemplate result =
_target.TryFindResource(new ComponentResourceKey(type, "AutomaticLockTemplate")) as ControlTemplate ??
_target.TryFindResource(new ComponentResourceKey(typeof(AutomaticLock), type.Name)) as ControlTemplate;
if(result!=null) return result;
}
return null;
}
}
This code will allow you to specify an automatic lock template on a control-by-control basis or it will allow you to use default templates defined either in the assembly containing the AutomaticLock class, in the assembly containing your custom control that the lock template applies to, in your local resources in your visual tree (including your application resources)
How to define AutomaticLock templates
Default templates for WPF standard controls are defined in the assembly containing the AutomaticLock class in a ResourceDictionary merged into Themes/Generic.xaml. For example, this template causes all TextBoxes to turn into TextBlocks when locked:
<ControlTemplate TargetType="{x:Type TextBox}"
x:Key="{ComponentResourceKey ResourceId=TextBox, TypeInTargetAssembly={x:Type lc:AutomaticLock}}">
<TextBlock Text="{TemplateBinding Text}" />
</ControlTemplate>
Default templates for custom controls may be defined in the assembly containing the custom control in a ResourceDictionary mered into its Themes/Generic.xaml. The ComponentResourceKey is different in this case, for example:
<ControlTemplate TargetType="{x:Type prefix:MyType}"
x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}">
...
If an application wants to override the standard AutomaticLock template for a specific type, it can place an automatic lock template in its App.xaml, Window XAML, UserControl XAML, or in the ResourceDictionary of an individual control. In each case the ComponentResourceKey should be specified the same way as for custom controls:
x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}"
Lastly, an automatic lock template can be applied to a single control by setting its AutomaticLock.LockTemplate property.
How to use AutomaticLock in your UI
To use automatic locking:
Set AutomaticLock.Enabled="True" on any controls that should be automatically locked. This can be done in a style or directly on individual controls. It enables locking on the control but does not cause the control to actually lock.
When you want to lock, set AutomaticLock.DoLock="True" on your top-level control (Window, view, UserControl, etc) whenever you want the automatic locking to actually happen. You can bind AutomaticLock.DoLock to a checkbox or menu item, or you can control it in code.
Some tips on effectively switching between view and edit modes
This AutomaticLock class is great for switching betwen view and edit modes even if they are significantly different. I have several different techniques for constructing my views to accomodate layout differences while editing. Some of them are:
Make controls invisible during edit or view mode by setting either their Template or AutomaticLockTemplate to an empty template as the case may be. For example, suppose "Age" is at the top of your layout in view mode and at the bottom in edit mode. Add a TextBox for "Age" in both places. In the top one set Template to the empty template so it doesn't show in Edit mode. In the bottom one set AutomaticLockTemplate to the empty template. Now only one will be visible at a time.
Use a ContentControl to replace borders, layout panels, buttons, etc surrounding content without affecting the content. The ContentControl's Template has the surrounding borders, panels, buttons, etc for edit mode. It also has an AutomaticLockTemplate that has the view mode version.
Use a Control to replace a rectangular section of your view. (By this I actually mean an object of class "Control", not a subclass therof.) Again, you put your edit mode version in the Template and your view mode version in the AutomaticLockTemplate.
Use a Grid with extra Auto-sized rows and columns. Use a trigger on the AutomaticLock.DoLock property to update the Row, Column, RowSpan, and ColumnSpan properties of the items within the Grid. For example you could move a panel containing an "Age" control to the top by changing its Grid.Row from 6 to 0.
Trigger on DoLock to apply a LayoutTranform or RenderTransform to your items, or to set other properties like Width and Height. This is useful if you want things to be bigger in edit mode, or if you want to make a TextBox wider and move the button beside it over against the edge.
Note that you can use option #3 (a Control object with separate templates for edit and view modes) for the entire view. This would be done if the edit and view modes were completely different. In this case AutomaticLock still gives you the convenience of being able to set the two templates manually. It would look like this:
<Control>
<Control.Template>
<ControlTemplate>
<!-- Edit mode view here -->
</ControlTemplate>
</Control.Template>
<lib:AutomaticLock.LockTemplate>
<ControlTemplate>
<!-- View mode view here -->
</ControlTemplate>
</lib:AutomaticLock.LockTemplate>
</Control>
Generally it is easier to tweak a few little positions and things between the edit and view modes, and better for your user experience because the user will have consistent layout, but if you do need a complete replacement AutomaticLock gives you that power as well.
<Grid>
<TextBlock Text="Name:"/>
<LabelText="{Binding Person.Name}" Cursor="IBeam" MouseDoubleClick="lblName_dblClick"/> <!-- set the IsEditMode to true inside this event -->
<TextBox Text="{Binding Person.Name}" Visibility="{Binding IsEditMode, Converter={StaticResource BoolToVisConverter}}"/>
<Button Content="OK" Click="btnSave_Click" Visibility="{Binding IsEditMode, Converter={StaticResource BoolToVisConverter}}"/> <!-- set the IsEditMode to false inside this event -->
</Grid>
Use a command rather, if you're familiar with.
I would create a single View with 2 different configoptions , f.e. 2 different constructors to make the the relevant field editable/readonly or visible/hidden
This way you don't write redundant XAML and you can configurate all fields over code behind or ViewModel when using MVVM
Sounds like a job for a DataTemplateSelector to me. If you would rather switch the individual controls in place I would do something similar to what Veer suggested.