Design-time data for ControlTemplate - wpf

Providing design-time data for DataContext is easy with use of d:DataContext but what about control properties referenced with {TemplateBinding} or {RelativeSource TemplatedParent} from Style.Template ?
Should I just populate control with sample data inside constructor/Loaded event when DesignerProperties.GetIsInDesignMode(this) returns true ?
(Can't do this since it would break normal design experience).
What about third party-controls that I can't modify ?

For my own controls I usually do something like:
<Style x:Key="FooStyle>
<Setter Property="Template>
<Setter.Value>
<ControlTemplate TargetType="FooControl">
<Grid d:DataContext="{d:DesignInstance FooDesignTimeData, IsDesignTimeCreatable=True}">
... guts of control template go here ...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Where "FooDesignTimeData" is a class that provides design time data in the appropriate form (implementing the interface from your runtime view model is a good practice here).
I don't see why this wouldn't work for a 3rd party control as well. You might not even have to retemplate the control -- you might be able to get away by just specifying the 3rd party control inside of your style and giving it a design time data context as specified above, but I haven't tried that scenario. I assume you're going to all this trouble because you're forced to use a control that does not have a great design time experience (such as by providing a Vendor.Controls.Design.dll or Vendor.Controls.Expression.Design.dll file).
To work with the TemplateBindings, I don't have a great solution. Usually I create a test page that displays my control and allows me to switch templates around. During integration you'll have an extra view (either within your app or as a separate app) that allows you to create and manipulate instances of the control as necessary. The GoToStateAction targeted trigger action from the Blend SDK is often useful here. For example, create a button for each visual state and then use the Click even to trigger a transition to a particular state. Thus you can easily test all of your states plus transitions while bound to test data. Hacky and not really design time data, but it works.

Related

Add control to logical-tree parent

My Main Window is a relatively simple DockPanel:
<DockPanel>
<!--Bottom row-->
<Border DockPanel.Dock="Bottom">
<DockPanel DockPanel.Dock="Bottom">
<!--Detector Indicator-->
<views:DetectorIndicatorView DataContext="{Binding DetectorViewModel}" DockPanel.Dock="Left"/>
<!--Logo-->
<Image DockPanel.Dock="Right" HorizontalAlignment="Right" Source="/Resources/Images/Logo.png"/>
</DockPanel>
</Border>
<!--Main display-->
<views:TabControlView DataContext="{Binding TabsViewModel}"/>
</DockPanel>
Inside the TabControl's SelectedContent there's a viewmodel for each tab, and each one of those can host a variety of viewmodels.
At one point, the tree basically looks like:
<MainWindow>
<TabControl>
<ExamTab>
<EditExam/>
</ExamTab>
</TabControl>
</MainWindow>
And on the EditExam page, I want a toolbar to appear in the Bottom row of the MainWindow. The toolbar buttons will be bound to commands ont he EditExamViewModel.
Is there a way I can "inject" an inner control like this into the "outer template" (i.e., the main window)?
The only way I can think of is to take the bottom row out of the MainWindow and paste it into each individual view, identical except for the one instance in EditExamView. Is that the only way?
Is there a way I can "inject" an inner control like this into the "outer template" (i.e., the main window)?
No, there is not, at least not in XAML.
You could it programmatically by for example getting a reference to the parent window in the view using the Window.GetWindow method, or by raising an event or send a message from the "tab" view model to the window view model or window using an event aggregator or a messenger.
What you describe is common in modern applications. Even menus are often swapped depending on the active view. However, there is no built-in way of doing this in XAML or WPF. The easiest way to solve this is
[...] to take the bottom row out of the MainWindow and paste it into each individual view, identical except for the one instance in EditExamView. [...]
The hardest way is to develop all that is needed for this scenario yourself including
Creating views and view models as data context dynamically
A concept to define areas where your views should be injected
Swapping in and out dependent views including their view models
Communication between views for synchronization or updating
Managing activation and deactivation of your exam tabs
Custom commands that you can wire accross views to your tab view
...
This is a lot of code that you might not be able to write on your own. But you are not alone, because there are application frameworks intended to bridge the gap between UI frameworks and your code like Caliburn Micro or Prism. These frameworks enable you to skip a lot of boilerplate code and provide services and mechanisms for various issues, but at the cost of learning them and some customization to fit your needs.
Prism as an example provides the following solutions to the issues above
A dependency injection container
Regions that define areas within your application
A rich region navigation service
An event aggregator for communication
Region adapters with active awareness
Composite Commands that can be used across views
...
This looks overwhelming at first, but the more you become familiar with a framework of your choice, the easier application development gets, as you will reuse concepts and components. At this point I recommend you not to re-invent the wheel. At first, you could use the easy solution and start getting familiar with any framework and gradually migrate your application.
So actually I’ve come up with a pretty good and simple solution, which is simply:
<ContentControl Content=“{Binding TabControlViewModel.Tabs[SelectedIndex].CurrentViewModel.ToolbarViewModel}”/>
If the current view model doesn’t have a toolbar view model then it’ll just evaluate to null and show nothing. Then I can define a data template for ToolbarViewModel and we’re all set.
If I wanted to enable various pages to have different things in the bottom bar, I could have ToolbarViewModel be some more general type (and probably a different name), possibly whatever my view model base is, and then any page’s VM could be any type of view model I want, and I could set the DataTemplate. The only caveat would be I don’t think I could have more than one template for any given VM, but I could get around that by creating simple subclasses for each way I’d want it to render so I could have different templates.

Set d:DesignHeight For All Pages

My WPF project consists of many Pages that all load into the same frame. I currently specify d:DesignHeight and d:DesignWidth in the Page tag in each individual file. I would like to just specify it in one place if possible (like a Style).
I tried setting it as a Style in my App.xaml.
<Style x:Key="PageStyle" TargetType="{x:Type Page}">
<Setter Property="d:DesignHeight" Value="942" />
<Setter Property="d:DesignWidth" Value="1264" />
The Pages render correctly in the designer, but the code fails to build:
Error: Cannot find the Style Property 'DesignHeight' on the type 'System.Windows.Controls.Page'
How can I set the same d:DesignHeight and d:DesignWidth for several pages in one place?
I'm afraid you can't. The designer properties are not real properties--they are directives that get ignored by the Xaml parser.
In fact, you can only use them when you explicitly tell the parser to ignore them:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
The mc:Ignorable directive tells the parser to ignore any lookup failures that occur within the given namespaces. If you attempt to reference one of the designer properties without adding the designer namespace to mc:Ignorable, you will get a compile-time error:
The property 'DesignWidth' does not exist in XML namespace 'http://schemas.microsoft.com/expression/blend/2008'.
As these are not real properties, you cannot apply them with a Setter, nor can you set them programmatically. Sorry.
Addendum
I spent some time investigating other options for setting the width and height in design mode. I tried custom attached properties, subclassing Page, and even overriding the default metadata for Width and Height in design mode. Invariably, I could get each approach working for almost any control except Window and Page. I suspect this is because of the way the designer displays windows and pages using special proxy elements.
At this point, I am reasonably confident there is no way to make this work with the VS 2017 Xaml designer.

Need advice on how to change colors

I'm building a WPF application that will be run on laptop computers mounted in police cars. The app has to have a "Night Mode" which will use darker colors to be less of a strain on the eyes at night.
Since the application is going to be used while the officer is driving, I've increased the size of all of the controls and I've defined new default templates for things like comboboxes. This is to make the screen easier to read at a glance and to make it easier to hit controls on the touch screen when you have sausage fingers, like I do.
I've created an enumeration called TimesOfDay. There are two values in the enumeration, DayTime and NightTime. Each control has a DepenencyProperty called TimeOfDay of the TimesOfDay enumeration type. There's a button on the main screen that you push to change the value of the TimeOfDay property. When you click the button, it cycles the TimeOfDay property's value between the two values.
I'm still pretty new to WPF, so I'm not sure how to go about this, on the Xaml side. I think what I need to do is create two named styles, for example one called DayStyle and another called NightStyle. Then I need to add triggers somewhere to change the style applied to the controls when the TimeOfDay property changes. Is that right?
Do I just change the background and foreground colors of the controls by type? Can I do it by element name?
I'm very fuzzy on all of this. Any help would be appreciated.
Tony
Each control has a DepenencyProperty called TimeOfDay of the TimesDay enumeration type.
Don't do that, just create two complete themes in separate ResourceDictionaries which you then can switch via the MergedDictionaries in the Application.Resources. There is no need to put the day-time information on the controls.
Follow this guide: http://weblogs.asp.net/psheriff/archive/2009/12/01/load-resource-dictionaries-at-runtime-in-wpf.aspx
Create various xaml resource files, but make sure the file does not compile and copies into the bin directory instead.
Decorate your xaml controls with DynamicResources.
Load in your resources through code.
Basically, you are looking to "skin" your application. The code that loads in your resource file can take advantage of the TimeOfDay enumeration.
If you want it automated you can even have some static class that has a timer to automatically attempt to change the resource and set the timer on the application startup. :)
I wouldn't duplicate the style, because I hate duplicated code... You could easily achieve that with a trigger in the ControlTemplate:
<Trigger Property="TimeOfDay" Value="NightTime">
<Setter TargetName="someControl" Property="Background" Value="Black" />
<Setter TargetName="someOtherControl" Property="ForeGround" Value="Yellow" />
...
</Trigger>
Another option is to use the technique I described here. This way you don't even need to put the TimeOfDay information on the control itself, it can be an ambient property.

WPF ControlTemplate partial replace

Suppose we have a very 'XAML long' ControlTemplate for our Control.
And we want to add just 1 Button to the Template.
MSDN claims that 'There is no way to replace only part of the visual tree of a control'.
Do I really have to copy all that ControlTemplate and add my Button to it?
Or: Is there any possibility how to provide some kind of an interface to a specific element? For example a TextBox, so that everyone could easily template it, without having to reapeat the whole ControlTemplate.
Thanks !
As qntmfred has said, you can't do this directly. However, if you own the control that you want to customise (which it sounds like you do), then you can do this by adding suitable customisation properties, such as a CustomButtons property (which each user of the control could add their own buttons to) or an ExtensionContent property (which each user of the control could set to whatever content they wanted -- leave it empty, add a single control, or add a panel with as many things as they wanted on it). Your ControlTemplate would assign no semantics to these properties, but just host them as they were given to it. For example, suppose you provided an ExtensionContent property. Your ControlTemplate could present this using a ContentPresenter:
<ContentPresenter ContentSource="ExtensionContent" />
And your users could put whatever they wanted in it:
<s:StefansControl>
<s:StefansControl.ExtensionContent>
<StackPanel Orientation="Horizontal">
<TextBlock Text="I'm custom content" />
<Button Click="DoSomethingSurprising_Click">Click me</Button>
<Image Source="something.jpg" />
</StackPanel>
</s:StefansControl.ExtensionContent>
</s:StefansControl>
Another possibly is to provide Style properties that you apply to parts of your control so that users can style them (including changing the template of that part (only) of the control) without replacing the entire style/template. This is kind of your "interface to a specific element" idea e.g. provide a FooBoxStyle property which gets applied to the "foo" TextBox.
In short, the idea is to build a certain measure of "partial replaceability" into the base template -- whether by using content, styles, templates or a combination of these. WPF doesn't provide a general notion of "partial replacement," but you can provide your own specific notion of partial replaceability provided you can predict what kind of partial replacements may be required.
MSDN is correct. Gotta copy the entire ControlTemplate.
Actually you can. It's not the most comfortable method, but here is how you do it:Cloning a WPF ControlTemplate

Is it possible to apply Blend behavior using style or template?

I'm very happy with my small collection of Blend behaviors, actions and triggers - they are easy to use and powerful. But I still can't figure out how to avoid applying them on per element basis. For example, if I have a behavior as such:
<Rectangle>
<i:Interaction.Behaviors>
<il:MouseDragElementBehavior/>
</i:Interaction.Behaviors>
</Rectangle>
and I have a few draggable rectangles in my Window, each of them has to have the above markup to be draggable. What I would like to do is to be able to write something like this:
<Style x:Key="RectangleStyle" TargetType="{x:Type Rectangle}">
<Setter Property="i:Interaction.Behaviors"
Value="il:MouseDragElementBehavior"/>
</Style>
It could be style, template, or some other way to avoid behavior or action markup repetition. The best solution I came up so far is creating a special behavior for the container (when attached, it enumerates children attaching to the children events).
Any ideas?
I ran into the same problem and I posted on my blog on how to create an attached property to work around this shortcoming of the Blend SDK.
I haven't tried it, but what I would probably do is create an attached property that can hold a collection of Behaviors. In the property changed handler of that property, I would enumerate the collection and add each of the behaviours to the real Interation.Behaviors property. It's a bit messy, but it ought to work.
Update
This approach won't work without a good deal more work: the problem is that Behaviors and Triggers can only be attached to one object at a time. This is probably why the limitation on applying them using a style exists in the first place. To get this to work you would need to create some kind of Trigger or Behavior factory.
You could create your own class that inherits from Rectangle and apply the behavior there.

Resources