Conditional XAML - wpf

For easy of development I'm using a ViewBox to wrap all content inside a Window. This is because my development machine has a smaller screen than the deployment machine so using a ViewBox allows for better realisation of proportion. Obviously there is no reason for it to be there on Release versions of the code. Is there an easy method to conditionally include/exclude that 'wrapping' ViewBox in XAML?
E.g.
<Window>
<Viewbox>
<UserControl /*Content*/>
</Viewbox>
</Window>

create two control templates in a resources dictionary somewhere accesible.
they should look like this
<ControlTemplate x:key="debug_view">
<ViewBox>
<ContentPresenter Content={Binding} />
</ViewBox>
</ControlTemplate>
<ControlTemplate x:key="release_view">
<ContentPresenter Content={Binding} />
</ControlTemplate>
then you could use this in your main view
<Window>
<ContentControl Template="{StaticResource debug_view}">
<UserControl /*Content*/ ...>
</ContentControl>
</Window>
then to switch back and forth just change the lookup key in the StaticResource Binding from 'debug_view' to 'release_view'
if you wanted to make it more dynamic you could additionally do this:
<Window>
<ContentControl Loaded="MainContentLoaded">
<UserControl /*Content*/ ...>
</ContentControl>
</Window>
then in your codebehind
void MainContentLoaded(object sender, RoutedEventArgs e)
{
ContentControl cc = (ContentControl) sender;
#if DEBUG
sender.Template = (ControlTemplate) Resources["debug_view"];
#else
sender.Template = (ControlTemplate) Resources["release_view"];
#endif
}
this way depending on whether or not the DEBUG symbol is defined different templates will be selected.

Related

Please teach me how to use ControlTemplate properly?

Ok, I think I am pretty noob on using ControlTemplate lol...
All I want is to template my every UserControl so every UserControl will have Delete, Save, Cancel buttons.
So now I am writting some test......
Try to make a border around the everything...
In App.xaml
<Application.Resources>
<ControlTemplate x:Key="DeleteSaveCancelTemplate">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border BorderThickness="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Black">
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</Application.Resources>
Then in UserControl:
<UserControl ... Height="150" Width="300" Template="{StaticResource DeleteSaveCancelTemplate}" >
But all I see is just ...black... what did I do wrong? I thought I should use ContentPresenter to shows the content?
For most controls you use ContentPresenter. ItemsPresenter only applies to elements that extend ItemsControl.
So... I found the problem lol From this page
The ControlPresenter doens't bind to Content by default...
I need to write
<ContentPresenter Content="{TemplateBinding Property=ContentControl.Content}" />
Unless you have specify the TargetType, then you it will bind by default.

Clickable ItemsControl item in WPF

I'm using an ItemsControl to display a list of databound items, for each the core of the DataTemplate is a Grid upon which I've placed all the bound controls.
I would like to be able to click on the entire area for each item in the list. But I cannot figure out how to make the area clickable.
Any suggestions of how to make an entire grid area clickable would be great.
To make something clickable you can usually wrap it in a Button, if it should be "invisible" you can change the template:
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter />
</ControlTemplate>
</Button.Template>
You can use the AttachedCommandBehavior classes from C# Disciples to achieve this.
Define a command in the ViewModel, and then on the Grid object use the ACB AttachedProperties to bind the MouseLeftButtonUp event to the command.
Some code to get you started:
<Grid Name="grid" Height="30" ForceCursor="True" Cursor="Hand">
<acb:CommandBehaviorCollection.Behaviors>
<acb:BehaviorBinding Event="MouseLeftButtonUp" Command="{Binding Path=DataContext.EditEventCommand, RelativeSource={RelativeSource AncestorType={x:Type self:Dashboard}}}" CommandParameter="{Binding}" />
</acb:CommandBehaviorCollection.Behaviors>
</Grid>
Edit for non-MVVM solution.
The above code snippet will still work when you have not designed your application following the MVVM guide-lines as you are essentially just binding to a command in the code-behind.
However, if you don't want to go to the trouble of defining commands, you can simply specify an event to hook to, like so:
<Grid MouseLeftButtonUp="Grid_MouseLeftButtonUp"> in the XAML file.
and in the code-behind:
private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
}

Hiding validation adornment when hiding a control

How, in WPF, do you hide the validation error template adornment (red box by default) when you hide a control? When I hide my controls (to facilitate switching between views) the error adornment sticks around.
Even more difficult, how do I do this using MVVM?
The default ControlTemplate for the Validation.ErrorTemplate has an AdornedElementPlaceholder which in turn has a reference to its AdornedElement. It looks like this
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</ControlTemplate>
From here would could bind the Visibility of the Border to the Visibility of the AdornedElementPlaceholder.AdornedElement to link their Visibility. Then we make all the Control's that has this problem use this Validation.ErrorTemplate instead of the default one. Here's an example
Xaml
<Window.Resources>
<ControlTemplate x:Key="ValidationErrorTamplate">
<Border Visibility="{Binding ElementName=placeHolder,
Path=AdornedElement.Visibility}"
BorderBrush="Red"
BorderThickness="1">
<AdornedElementPlaceholder x:Name="placeHolder"/>
</Border>
</ControlTemplate>
</Window.Resources>
<TextBox ...
Validation.ErrorTemplate="{StaticResource ValidationErrorTamplate}">
Update
To reference the parent UserControl in the binding you can
1.For a specific control you can walk up the logical tree using the Parent Property
Example: If the TextBox is located in a StackPanel in the UserControl we can reference it with Parent.Parent
<UserControl ...>
<StackPanel>
<TextBox ...
Validation.ErrorTemplate="{StaticResource ValidationErrorTamplate2}">
<ControlTemplate x:Key="ValidationErrorTamplate2">
<Border Visibility="{Binding ElementName=placeHolder,
Path=AdornedElement.Parent.Parent.Visibility}"
BorderBrush="Red"
BorderThickness="1">
<AdornedElementPlaceholder x:Name="placeHolder"/>
</Border>
</ControlTemplate>
2.For a more dynamic approach you can use a ResourceDictionary with a code behind file where you make use of the Loaded event for the Border. In it, you walk up the visual tree to find the parent UserControl and use that as the source for the Binding
ValidationErrorTemplateDictionary.xaml
<ResourceDictionary x:Class="ValidationErrorVisibility.ValidationErrorTemplateDictionary"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTemplate x:Key="ValidationErrorTamplate3">
<Border BorderBrush="Red"
BorderThickness="1"
Loaded="ValidationAdorner_Loaded">
<AdornedElementPlaceholder/>
</Border>
</ControlTemplate>
</ResourceDictionary>
ValidationErrorTemplateDictionary.xaml.cs
public partial class ValidationErrorTemplateDictionary
{
private void ValidationAdorner_Loaded(object sender, RoutedEventArgs e)
{
Border adornedBorder = sender as Border;
Binding visibilityBinding = new Binding("Visibility");
UIElement adornedElement = ((AdornedElementPlaceholder)adornedBorder.Child).AdornedElement;
UserControl parentUserControl = GetVisualParent<UserControl>(adornedElement);
visibilityBinding.Source = parentUserControl;
adornedBorder.SetBinding(Border.VisibilityProperty, visibilityBinding);
}
public static T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
}
Your UserControl
<UserControl ...>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ValidationErrorTemplateDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel>
<TextBox ...
Validation.ErrorTemplate="{StaticResource ValidationErrorTamplate3}">
I've just had to solve this very problem, for visibility AND opacity.
I did it by creating an inherited attached property which I data bind the ErrorTemplate visibility and opacity to. On the parent element (the actual element that is fading in and out / being collapsed) I simply bind the new attached properties to visibility and opacity respectively.
This method uses WPF's logical tree and existing property value inheritance to solve the problem without code behind, or specific knowledge by your template of what the visibility-controlling parent will be.
In hindsight, I could have created a single attached property of type FrameWorkElement which I can then use to bind on any property on the parent element. This approach would involve less binding and less code to achieve while providing a little more flexibility. Perhaps an attached property already exists to let you do the same thing.
You can read all about attached properties right here: http://msdn.microsoft.com/en-us/library/ms749011.aspx
Alternatively, this is a good stack:
How exactly do Attached Properties work in WPF?

How to use template binding inside data template in custom control (Silverlight)

I am trying to create control which will take ItemsSource and InnerTemplate and will show all the items wrapped in CheckBoxes.
The control has 2 dependency properties:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CheckBoxWrapperList), null);
public static readonly DependencyProperty InnerTemplateProperty = DependencyProperty.Register("InnerTemplate", typeof(DataTemplate), typeof(CheckBoxWrapperList), null);
and here is the template:
<ControlTemplate TargetType="local:CheckBoxWrapperList">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="wrapper">
<CheckBox>
<ContentPresenter ContentTemplate="{TemplateBinding InnerTemplate}" Content="{Binding}" />
</CheckBox>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemTemplate="{StaticResource wrapper}" ItemsSource="{TemplateBinding ItemsSource}" />
</Grid>
</ControlTemplate>
However, this approach does not work.
Binding in the ControlPresenter.ContentTemplate using TemplateBinding does not work.
However, when I don't use template binding and reference the template as static resource, then it works as expected.
Why cannot I use the template binding inside the content presenter in datatemplate?
What am I missing here? Any special markup required?
Is there a way to achieve the expected behavior?
Thanks in advance.
Silverlight and WPF
You can get around this with a relative source binding:
Instead of:
{TemplateBinding InnerTemplate}
You would use:
{Binding RelativeSource={RelativeSource AncestorType=local:CheckBoxWrapperList}, Path=InnerTemplate}
It's a bit messier but it works.
WinRT
WinRT doesn't have AncestorType. I've got something that works but it's kind of horrifying.
You can use an attached property to store a TemplateBinding value and then access it using ElementName...
<ControlTemplate TargetType="local:CheckBoxWrapperList">
<Grid x:Name="TemplateGrid" magic:Magic.MagicAttachedProperty="{TemplateBinding InnerTemplate}">
<Grid.Resources>
<DataTemplate x:Key="wrapper">
<CheckBox>
<ContentPresenter ContentTemplate="{Binding ElementName=TemplateGrid, Path=(magic:Magic.MagicAttachedProperty)}" Content="{Binding}" />
</CheckBox>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemTemplate="{StaticResource wrapper}" ItemsSource="{TemplateBinding ItemsSource}" />
</Grid>
</ControlTemplate>
I don't know if there's a better way for WinRT.
TemplateBinding can only be used within a ControlTemplate, you're using it within a DataTemplate. (The fact that the DataTemplate is within a ControlTemplate doesn't matter)

Prevent WPF control from expanding beyond viewable area

I have an ItemsControl in my user control with a scroll viewer around it for when it gets too big (Too big being content is larger than the viewable area of the UserControl). The problem is that the grid that it is all in just keeps expanding so that the scroll viewer never kicks in (unless I specify an exact height for the grid). See code below and thanks in advance.
<UserControl x:Class="BusinessObjectCreationWizard.View.TableSelectionPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<GroupBox FontWeight="Bold" Height="300px"
Header="Tables"
Padding="2">
<ScrollViewer>
<ItemsControl FontWeight="Normal"
ItemsSource="{Binding Path=AvailableTables}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=DisplayName}"
IsChecked="{Binding Path=IsSelected}"
Margin="2,3.5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
</UserControl>
This user control is loaded here
<Border Background="White" Grid.Column="1" Grid.Row="0">
<HeaderedContentControl Content="{Binding Path=CurrentPage}"
Header="{Binding Path=CurrentPage.DisplayName}" />
</Border>
I would like to not specify the height.
If you remove the Height from your GroupBox (which, as far as I understand, is what you want to do), then it will fill its container, unless there's a panel upstream that imposes its own sizing rules.
I used this simplified version of your XAML. I removed the template and the binding, and hard-coded some items, to make this stand alone; those changes won't affect the way layout is done.
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<GroupBox FontWeight="Bold" Header="Tables" Padding="2">
<ScrollViewer>
<ItemsControl FontWeight="Normal">
<TextBlock>Foo</TextBlock>
<TextBlock>Bar</TextBlock>
<TextBlock>Baz</TextBlock>
</ItemsControl>
</ScrollViewer>
</GroupBox>
</Window>
Run it, and you'll see that the content does indeed size to fit the window, and the scrollbar only enables when the window gets too small to see all three items. I believe this is what you want.
So the problem is most likely one of the parent panels, one you're not showing in your sample XAML. The problem you describe could occur if your GroupBox appears inside a StackPanel:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<StackPanel>
<GroupBox FontWeight="Bold" Header="Tables" Padding="2">
<ScrollViewer>
<ItemsControl FontWeight="Normal">
<TextBlock>Foo</TextBlock>
<TextBlock>Bar</TextBlock>
<TextBlock>Baz</TextBlock>
</ItemsControl>
</ScrollViewer>
</GroupBox>
</StackPanel>
</Window>
Now the GroupBox appears at the top of the Window, sized to exactly fit its contents. If you shrink the Window enough, the GroupBox will be cut off -- because it's sized to fit its content, not its container. This sounds like the problem you're describing.
The reason is that StackPanel asks its children what their ideal height is (based on their content), and uses that height. Without StackPanel (or something similar), the default is to respect the control's VerticalAlignment, and if that's set to the default value of Stretch, then the control is stretched to fill its parent. This means it won't be taller than its parent, which sounds like what you want.
Solution: remove the StackPanel (or whatever else is causing you problems) and use something else. Depending on what you're trying to accomplish, you might have better luck with a DockPanel or a Grid. Hard to tell without knowing more about your layout.
Edit: Okay, it looks like the problem is indeed the HeaderedContentControl parent -- but not directly. HeaderedContentControl isn't a panel, so it doesn't do any layout of its own (and its descendant, GroupBox, doesn't have this same problem). The problem is its default template -- which includes a StackPanel. The good news is, you're free to use a different template, let's say one with a DockPanel instead:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<HeaderedContentControl>
<HeaderedContentControl.Style>
<Style TargetType="{x:Type HeaderedContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type HeaderedContentControl}">
<DockPanel>
<ContentPresenter ContentSource="Header" DockPanel.Dock="Top"/>
<ContentPresenter/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</HeaderedContentControl.Style>
<GroupBox FontWeight="Bold" Header="Tables" Padding="2">
<ScrollViewer>
<ItemsControl FontWeight="Normal">
<TextBlock>Foo</TextBlock>
<TextBlock>Bar</TextBlock>
<TextBlock>Baz</TextBlock>
</ItemsControl>
</ScrollViewer>
</GroupBox>
</HeaderedContentControl>
</Window>
If you leave off the <HeaderedContentControl.Style> part, this reproduces your problem; but with the style in place, it allows the GroupBox to fill its container, so the ScrollViewer will get a scrollbar when you want it to.
If the previous answer doesn't fix the problem, you could also try binding the Width, Height of your grid to the ActualWidth, ActualHeight of your parent UserControl. Something like:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication.UserControl1"
x:Name="UserControl">
<Grid Height="{Binding ElementName=UserControl, Path=ActualHeight}"
Width="{Binding ElementName=UserControl, Path=ActualWidth}" />
In this case you aren't setting an explicit width and height but you are limiting the Grids width/height to the constraints of the UserControl it sits in.
I had the same issue, after reading this response I replaced all StackPanels with Grids in UserControl. It resolved the Scrollbar issue.
Try removing the grid entirely and setting the HorizontalAlignment and VerticalAlignment directly on the GroupBox. If a layoutpanel has only one child, it's often redundant... this migth be true in your case.
If that doesn't work... what's the parent of your grid control?
Why not just use a listbox instead of an itemscontrol, that has a built in scrollviewer.
They are different. If you do not want to have the items selectable, then don't use a ListBox. It is going to be heavier, and will also have the deselect a selection everytime the user clicks on an entry. Just put the ItemsControl in a ScrollViewer
I had the same problema with ListBox, it wasn't expanding and the scroll viewer didn't appear. I solved it as follows:
<UserControl x:Class="TesteView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid MaxHeight="710">
....
....
<StackPanel>
<ListBox MaxHeight="515"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Path=Teste,Mode=TwoWay}">
....
....
</ListBox>
</StackPanel>
</Grid>
</UserControl>

Resources