XamlParseException on extended ListView in Windows Phone 8.1 - c#

I've been scouring the internet for a week looking for an answer to my question, but nobody else seems to be hitting this issue.
I am migrating an app from Windows Phone 8 Silverlight to Windows Phone 8.1 WinRT, and am having an issue with a custom control I made. In the Silverlight app, I created a custom LongListSelector that I predefined a ItemTemplate for with custom binding and logic. I reuse this LongListSelector in a few different places in the app.
I am trying to do the same thing in my WinRT app, but with ListView. The issue is that when I try to include my custom extended ListView in any XAML page, I get an E_UNKNOWN_ERROR XamlParseException with the Line and Position number set to the end of the opening tag of my ListView.
Here is what my custom ListView's XAML looks like:
<ListView
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyAppNamespace"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="using;System;assembly=mscorlib"
x:Class="MyAppNamespace.CustomListView"
x:Name="This"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
.... my data template is here ...
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And my code-behind is this:
namespace MyAppNamespace
{
public partial class CustomListView: ListView
{
public CustomListView()
{
this.InitializeComponent();
}
... event handlers and custom logic here ...
}
}
And here's how I reference it in another XAML page
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyAppNamespace"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="MyAppNamespace.SamplePage"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<local:CustomListView DataContext="{Binding Items}"/>
</Grid>
</Page>
The error shows up in Blend and in the design view of the xaml file in Visual Studio. When I run the app and navigate to the page where I am using this control, the error appears on the LoadComponent function call within the generated InitializeComponent.
The weird thing is that if I switch the root element to UserControl and put the ListView inside it (and update the base class in the code-behind), then everything works fine, but I'd rather just directly extend the ListView then wrap it in a UserControl.

Finally figured it out!!
I needed to override the PrepareContainerForItemOverride and then set the Loaded event for the ListViewItem passed in as the first parameter. Then, in the event handler, apply the callbacks as needed by traversing the element tree.
PrepareContainerForItemOverride will be called every time a row of the ExtendedListView is populated with a different element from the ItemsSource, but the Loaded callback will only be called once per row unless the row is unloaded (you can also add a callback handler for this if needed.
Below is some sample code to help out anyone else who has this issue!!
Here's the relevant contents of ExtendedListView.cs:
public sealed class ExtendedListView : ListView
{
public ExtendedListView()
{
this.DefaultStyleKey = typeof(ExtendedListView);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
ListViewItem lvi = element as ListViewItem;
lvi.Loaded += lvi_Loaded;
}
void lvi_Loaded(object sender, RoutedEventArgs e)
{
ListViewItem lvi = sender as ListViewItem;
ApplyCallbacksToElement(lvi.ContentTemplateRoot);
}
private void ApplyCallbacksToElement(DependencyObject element)
{
if (null != element)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(element, i);
// Code for adding element callbacks goes here
//
// For example:
// if (IsButtonAndMatchesCondition(child))
// {
// (child as Button).Click += button_Click;
// }
//
ApplyCallbacksToElement(child);
}
}
}
}
And here's the relevant content of the Generic.xaml file generated in the Themes folder created after making a new TemplatedControl:
<Style TargetType="local:ExtendedListView">
<Setter Property="IsTabStop"
Value="False" />
<Setter Property="TabNavigation"
Value="Once" />
<Setter Property="IsSwipeEnabled"
Value="True" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility"
Value="Auto" />
<Setter Property="ScrollViewer.HorizontalScrollMode"
Value="Disabled" />
<Setter Property="ScrollViewer.IsHorizontalRailEnabled"
Value="False" />
<Setter Property="ScrollViewer.VerticalScrollMode"
Value="Enabled" />
<Setter Property="ScrollViewer.IsVerticalRailEnabled"
Value="False" />
<Setter Property="ScrollViewer.ZoomMode"
Value="Disabled" />
<Setter Property="ScrollViewer.IsDeferredScrollingEnabled"
Value="False" />
<Setter Property="ScrollViewer.BringIntoViewOnFocusChange"
Value="True" />
<Setter Property="ItemContainerTransitions">
<Setter.Value>
<TransitionCollection>
<AddDeleteThemeTransition />
<ContentThemeTransition />
<ReorderThemeTransition />
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<!-- Put your custom DataTemplate here -->
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ExtendedListView">
<Border BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer x:Name="ScrollViewer"
TabNavigation="{TemplateBinding TabNavigation}"
HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}"
VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}"
IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}"
IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}"
AutomationProperties.AccessibilityView="Raw">
<ItemsPresenter Header="{TemplateBinding Header}"
HeaderTemplate="{TemplateBinding HeaderTemplate}"
HeaderTransitions="{TemplateBinding HeaderTransitions}"
Footer="{TemplateBinding Footer}"
FooterTemplate="{TemplateBinding FooterTemplate}"
FooterTransitions="{TemplateBinding FooterTransitions}"
Padding="{TemplateBinding Padding}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

You can only extend controls that inherit from UserControl the way you are trying (Like Page for example).
What you have to do is just create a class that inherits from ListView and modify default style for it.
<Style TargetType="my:CustomListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="..">
...

Related

Removing full screen in xaml dynamically

This will probably be very simple for most of you, I am new to XAML and WPF.
I have an app that startes att full screen, I did this by adding
WindowState="Maximized"
WindowStyle="None"
I want to have a button that simply eliminates this part. I have a "Full screen" button in the xaml and by click he is calling a "FullScreen_Click" function in my code.
I just need to know what to write in the code that will eliminate the full screen if it is on full screen mode and restore it to full screen when it is not.
Try this:
private void FullScreen_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}
This will toggle between WindowState.Maximized and WindowState.Normal each time the Button is clicked.
For a xaml only technique just for reference to see a xaml example in comparison (but I would do #mm8's route, it's simpler);
1. Bind your property to that of another like:
<Window WindowState="{Binding Tag, ElementName=toggleState}" .... />
2. Use a `ToggleButton` or similar control and `Triggers`
.
<!-- like this PoC -->
<Grid>
<Grid.Resources>
<Style x:Key="cwWindowState_PoC" TargetType="{x:Type ToggleButton}">
<Setter Property="Tag" Value="Maximized"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Border Background="{TemplateBinding Background}"/>
<ContentPresenter x:Name="MyContentPresenter"
Content="{TemplateBinding Tag}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Tag" Value="Normal" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Tag" Value="Maximized" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ToggleButton x:Name="toggleState" Content="Click Me"
Background="Green"
Style="{StaticResource cwWindowState_PoC}"/>
</Grid>
Could also use DataTrigger but that requires interaction triggers instead of just a property setter from a template.

Why does a custom control "ImageButton" not display it's image?

I'm writing an image button custom control with highlighting effects, based on MahApps' AccentedSquareButtonStyle. ImageButton.xaml:
<UserControl x:Class="NQR_GUI_WPF.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:NQR_GUI_WPF"
mc:Ignorable="d" >
<Button Style="{StaticResource AccentedSquareButtonStyle}" Background="Transparent" Foreground="Transparent" BorderThickness="0" Width="24" Height="24" TouchDown="Button_TouchDown">
<Grid Background="Transparent">
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Content" Value="{Binding Image, RelativeSource={RelativeSource TemplatedParent}}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=IsMouseOver}" Value="True" >
<Setter Property="Content" Value="{Binding HighlightedImage, RelativeSource={RelativeSource TemplatedParent}}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=IsPressed}" Value="True" >
<Setter Property="Content" Value="{Binding ClickedImage, RelativeSource={RelativeSource TemplatedParent}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</Button>
ImageButton.xaml.cs:
namespace NQR_GUI_WPF
{
/// <summary>
/// Interaction logic for ImageButton.xaml
/// </summary>
public partial class ImageButton : UserControl
{
public static DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(Canvas), typeof(ImageButton));
public static DependencyProperty ClickedImageProperty = DependencyProperty.Register("ClickedImage", typeof(Canvas), typeof(ImageButton));
public static DependencyProperty HighlightedImageProperty = DependencyProperty.Register("HighlightedImage", typeof(Canvas), typeof(ImageButton));
static ImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
}
public Canvas Image
{
get { return (Canvas)base.GetValue(ImageProperty); }
set { base.SetValue(ImageProperty, value); }
}
public Canvas ClickedImage
{
get { return (Canvas)base.GetValue(ClickedImageProperty); }
set { base.SetValue(ClickedImageProperty, value); }
}
public Canvas HighlightedImage
{
get { return (Canvas)base.GetValue(HighlightedImageProperty); }
set { base.SetValue(HighlightedImageProperty, value); }
}
private void Button_TouchDown(object sender, TouchEventArgs e)
{
Keyboard.ClearFocus();
}
}
}
Example icon:
<Canvas x:Key="printIcon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="appbar_printer_text" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
<Path Width="44" Height="45" Canvas.Left="16" Canvas.Top="17" Stretch="Fill" Fill="{Binding Source={x:Static prop:Settings.Default}, Path=theme, Converter={StaticResource idealForegroundConverter}}" Data="F1 M 25,27L 25,17L 51,17L 51,27L 47,27L 47,21L 29,21L 29,27L 25,27 Z M 16,28L 60,28L 60,51L 52,51L 52,46L 55,46L 55,33L 21,33L 21,46L 24,46L 24,51L 16,51L 16,28 Z M 25,39L 28,39L 28,52L 35,52L 35,59L 48,59L 48,39L 51,39L 51,62L 33,62L 25,54L 25,39 Z M 46,55L 38,55L 38,52L 46,52L 46,55 Z M 46,49L 30,49L 30,46L 46,46L 46,49 Z M 46,43L 30,43L 30,40L 46,40L 46,43 Z "/>
</Canvas>
The problem is that in MainWindow, after adding the images stored in App.xaml, the control is empty (no images are shown).
<local:ImageButton Image="{StaticResource printIcon}" HighlightedImage="{StaticResource printIconHighlighted}" ClickedImage="{StaticResource printIconClicked}" Grid.Column="1" HorizontalAlignment="Left" Height="46" Margin="36,10,0,0" VerticalAlignment="Top" Width="100"/>
I have tried binding the images directly into the control template, but without success (although in the control designer view the image is shown). Why aren't the control images displayed?
A UserControl isn't your best option for this. UserControls aren't meant for writing general-purpose WPF controls. You can do it, but it's not the simplest way. The simplest way is to subclass a regular control (often just ContentControl or HeaderedContentControl), then write a style and a template for it. Once you get this technique nailed down you can just bang 'em out as needed. Often you can just write a specialized template for an existing control, but in your case you do need your own subclass of Button.
I would write ImageButton as a subclass of Button, with the additional dependency properties pretty much as you've defined them, but I'd make them of type Object so a consumer can stuff anything in there that XAML can render. No reason not to give them all the rope they can use. And I'll use the Content property instead of the Image property, because that simplifies things.
If for some reason you have a requirement to prevent non-image content, you could use a more specialized content type than Object, but you didn't mention any particular reason for introducing that limitation.
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace NQR_GUI_WPF
{
/// <summary>
/// Interaction logic for ImageButton.xaml
/// </summary>
public class ImageButton : Button
{
public ImageButton()
{
TouchDown += ImageButton_TouchDown;
}
private void ImageButton_TouchDown(object sender, TouchEventArgs e)
{
Keyboard.ClearFocus();
}
#region Dependency Properties
public static DependencyProperty ClickedContentProperty = DependencyProperty.Register("ClickedContent", typeof(Object), typeof(ImageButton));
public static DependencyProperty HighlightedContentProperty = DependencyProperty.Register("HighlightedContent", typeof(Object), typeof(ImageButton));
public Object ClickedContent
{
get { return (Object)base.GetValue(ClickedContentProperty); }
set { base.SetValue(ClickedContentProperty, value); }
}
public Object HighlightedContent
{
get { return (Object)base.GetValue(HighlightedContentProperty); }
set { base.SetValue(HighlightedContentProperty, value); }
}
#endregion Dependency Properties
}
}
XAML resoure dictionary ImageButton.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:nqrgui="clr-namespace:NQR_GUI_WPF"
>
<Style TargetType="{x:Type nqrgui:ImageButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type nqrgui:ImageButton}">
<Grid>
<ContentControl
Content="{TemplateBinding Content}"
x:Name="PART_Content"
/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter
TargetName="PART_Content"
Property="Content"
Value="{Binding HighlightedContent, RelativeSource={RelativeSource TemplatedParent}}"
/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter
TargetName="PART_Content"
Property="Content"
Value="{Binding ClickedContent, RelativeSource={RelativeSource TemplatedParent}}"
/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And here's how you'd use it:
<Window
...
xmlns:nqrgui="clr-namespace:NQR_GUI_WPF"
...
>
<!-- Or better yet, merge ImageButton.xaml in App.xaml so everybody can see it -->
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ImageButton.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
...
<!-- As noted, Content, HighlightedContent, and ClickedContent
can be images -- or also paths, text, ANYTHING XAML can render.
-->
<nqrgui:ImageButton
Content="Content"
HighlightedContent="Highlighted"
ClickedContent="Clicked"
/>
And you really can go absolutely berserk with the content:
<!-- Don't try this in a UI anybody will have to use! -->
<nqrgui:ImageButton
Content="Content"
ClickedContent="Clicked"
>
<nqrgui:ImageButton.HighlightedContent>
<StackPanel Orientation="Horizontal">
<Border
BorderBrush="Gray"
Background="GhostWhite"
BorderThickness="1">
<Path
Width="20"
Height="20"
Stroke="Black"
StrokeThickness="2"
Data="M 0,0 L 20,20 M 0,20 L 20,0"
Margin="2"
/>
</Border>
<nqrgui:ImageButton
Content="LOL"
ClickedContent="Don't Click Me, Bro!"
HighlightedContent="I heard you like buttons"
/>
</StackPanel>
</nqrgui:ImageButton.HighlightedContent>
</nqrgui:ImageButton>
You are using TemplateParent incorrectly
instead of this
{Binding Image, RelativeSource={RelativeSource TemplatedParent}}
it should be something like this
{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ImageButton}, Path=Image}
I have done that like this below,
<Controls:MetroWindow.Resources>
<ImageBrush Stretch="Fill" x:Key="CloseImage" ImageSource="../images/Close.png" />
<ImageBrush x:Key="CloseImageRed" ImageSource="../images/CloseRed.jpg" />
</Controls:MetroWindow.Resources>
<Button>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource CloseImageRed}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
RecognizesAccessKey="True"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource CloseImage}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Have a look.
You are setting UserControl.Content to your customized Button, and I think what you want to be setting is UserControl.ContentTemplate.
From within .Content, there is no "TemplatedParent" to bind to. However if this were a Template, then TemplatedParent would point to the UserControl that the Template is defined for. In this case, it would refer to your ImageButton UserControl, which would correctly give you access to the Image properties.
<UserControl ..>
<UserControl.ContentTemplate>
<ControlTemplate>
<!-- TemplatedParent bindings should refer to UserControl from here -->
<Button ... />
</ControlTemplate>
</UserControl.ContentTemplate>
</UserControl>
This also allows you to write something like
<local:ImageButton Content="Some Text" />
without completely replacing your Button XAML code with a Text element containing "Some Text"
For an example, what you have right now would render as
<UserControl>
<Button /> <!-- Button is .Content, and can be replaced by XAML using the control -->
</UserControl>
While if it were a ContentTemplate, it would render as
<UserControl>
<Button> <!-- Button is ContentTemplate, so wraps any content given by external XAML -->
<Content />
</Button>
</UserControl>

WPF Custom Control Button Content Goes Missing With More Than One Button

To begin with, this is in .NET 4.0 because it has to be. I know some bugs have been fixed in later versions of .NET, so if this is an actual .NET bug, I guess I'm going to have to live with using user controls which don't seem to have this issue.
I created a custom control library in WPF to make customizable buttons that will be used in 3rd party software. I seem to have an issue, however, with multiple buttons resulting in the content for all but one of the buttons to go missing. I have confirmed the problem in SNOOP. The content just isn't there. The SNOOP tree gets as far as the content presenter and then there's nothing under it, except for the one button that does have content. I've created a very bare bones example of the problem.
My Library's Generic.xaml is as follows:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CustomControlsLibrary.Controls">
<Style x:Key="CustomButtonStyle" TargetType="{x:Type controls:CustomButton}">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:CustomButton}">
<Border CornerRadius="{TemplateBinding CornerRadius}" BorderThickness="3" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" ContentSource="Content" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Button1Style" TargetType="{x:Type controls:Button1}" BasedOn="{StaticResource CustomButtonStyle}" >
<Setter Property="CornerRadius" Value="4" />
<Setter Property="BorderBrush" Value="White" />
<Setter Property="Height" Value="40" />
<Setter Property="Width" Value="100" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=controls:Button1}, Path=Text}" />
</Setter.Value>
</Setter>
</Style>
The two control classes are as follows:
CustomButton:
public class CustomButton : Button
{
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(CustomButton), new FrameworkPropertyMetadata(new CornerRadius(0)));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
}
Button1:
public class Button1 : CustomButton
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Button1), new FrameworkPropertyMetadata(""));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
static Button1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Button1), new FrameworkPropertyMetadata(typeof(Button1)));
}
}
I then create a simple WPF application with just a main window with all logic in MainWindow.xaml:
<Window x:Class="CustomControlLibraryTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CustomControlsLibrary.Controls;assembly=CustomControlsLibrary"
Title="MainWindow" Height="350" Width="525" Background="DarkGray">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/CustomControlsLibrary;component/Themes/Generic.xaml" />
</Window.Resources>
<StackPanel>
<controls:Button1 Style="{StaticResource Button1Style}" Background="Red" Text="Button 1" />
<controls:Button1 Style="{StaticResource Button1Style}" Background="Blue" Text="Button 2" />
</StackPanel>
When run, the content for Button 1 goes missing while Button 2 looks just fine. Removing Button 2 from the Window causes Button 1 to look as expected.
And as mentioned earlier, SNOOP indicates that Button 1's content is just not there when both buttons are present.
Any ideas?
I'm going to throw in a dissenting opinion here, starting with a quote from Matthew MacDonalds "Pro WPF in C#":
Custom controls are still a useful way to build custom widgets that
you can share between applications, but they’re no longer a
requirement when you want to enhance and customize core controls. (To
understand how remarkable this change is, it helps to point out that
this book’s predecessor, Pro .NET 2.0 Windows Forms and Custom
Controls in C#, had nine complete chapters about custom controls and
additional examples in other chapters. But in this book, you’ve made
it to Chapter 18 without a single custom control sighting!)
Put simply, there is just no need to be creating extra button classes just to control properties that already exist in the templates. You can do that just as easily with data binding or attached properties etc and it will be a lot more compatible with tools like Blend.
To illustrate the point here's a helper class for the two properties you're exposing in your sample code:
public static class ButtonHelper
{
public static double GetCornerRadius(DependencyObject obj)
{
return (double)obj.GetValue(CornerRadiusProperty);
}
public static void SetCornerRadius(DependencyObject obj, double value)
{
obj.SetValue(CornerRadiusProperty, value);
}
// Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.RegisterAttached("CornerRadius", typeof(double), typeof(ButtonHelper), new PropertyMetadata(0.0));
public static string GetButtonText(DependencyObject obj)
{
return (string)obj.GetValue(ButtonTextProperty);
}
public static void SetButtonText(DependencyObject obj, string value)
{
obj.SetValue(ButtonTextProperty, value);
}
// Using a DependencyProperty as the backing store for ButtonText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonTextProperty =
DependencyProperty.RegisterAttached("ButtonText", typeof(string), typeof(ButtonHelper), new PropertyMetadata(""));
}
Now we can immediately create two style, one for each of your button types, that bind to these properties internally:
<Style x:Key="RoundedButtonStyle" TargetType="{x:Type Button}" >
<Setter Property="Margin" Value="10" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="Background" Value="Red" />
<Setter Property="controls:ButtonHelper.CornerRadius" Value="4" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="{Binding Path=(controls:ButtonHelper.CornerRadius),
RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="3"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" ContentSource="Content" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TextButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource RoundedButtonStyle}">
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Background" Value="Blue" />
<Setter Property="controls:ButtonHelper.ButtonText" Value="TextButton" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="{Binding Path=(controls:ButtonHelper.CornerRadius),
RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="3"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<TextBlock Text="{Binding Path=(controls:ButtonHelper.ButtonText),
RelativeSource={RelativeSource TemplatedParent}}" Background="Transparent" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
That's it! No custom control needed no need for x:Shared due to content being specified directly in a style, and it's a lot more light-weight. Here's an example of them being used:
<UniformGrid Columns="2">
<Button Style="{StaticResource RoundedButtonStyle}" Content="RoundedButton" />
<Button Style="{StaticResource RoundedButtonStyle}" Content="RoundedButton big radius" controls:ButtonHelper.CornerRadius="20"/>
<Button Style="{StaticResource TextButtonStyle}" />
<Button Style="{StaticResource TextButtonStyle}" controls:ButtonHelper.ButtonText="TextButton new text"/>
<Button Style="{StaticResource TextButtonStyle}" BorderBrush="Green" Background="Green"
controls:ButtonHelper.ButtonText="Both text and radius"
controls:ButtonHelper.CornerRadius="20" />
</UniformGrid>
And here's the result:
I do realize of course that I've specified the border in each template, but that too can be easily removed by placing a content control inside the border and using data templating to set the content.
What's happening is that the style actually has a single TextBlock instance. When the style is applied to the second button the TextBlock is actually re-parented to the new control. You should be able to avoid this by setting x:Shared="false" on the TextBlock element.

Windows 8 metro app: styling a button dynamically. C# and Xaml

I have a xaml page with Gridview which has a Button.I am using a grouped Item page. The buttons are generated based on the dataset returned.
10 records will display 10 buttons.
<GridView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left" Width="Auto" Height="Auto" >
<Button Click="ItemView_ItemClick">
<StackPanel Margin="5" >
<TextBlock Tag="cntCustName" Style="{ThemeResource CntNormalTextBlockStyle}" Text="{Binding CUSTOMERNAME }"/>
<TextBlock Tag="cntCatCode" Style="{ThemeResource CntLrgTextBlockStyle}" Text="{Binding CATEGORYCODE}"/>
<TextBlock Tag="cntDay" Style="{ThemeResource CntNormalTextBlockStyle}" Text="{Binding DAY}"/>
</StackPanel>
</Button>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
I am getting data in a foreach loop. I want to be able to assign the styles of the buttons
dynamically based on some criteria which I have.
foreach (ContactData ContactData in _openContatcs)
{
if (ContactData.CFLAG)
{
//this is an example
Application.Current.Resources["contactSquare"] = ampStyle;
}
group.Items.Add(ContactData);
}
Styles are defined like this in a folder: Assests/Resources/BaseAPStyles.xaml
<Style x:Name="contactSquare" TargetType="Button">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Height" Value="160"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="Width" Value="160"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid Background="#ffffc600">
<ContentPresenter>
</ContentPresenter>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My buttons are not showing the styles. How can I achieve this?
use x:key="contactSqaure" to find the style in the application resources.
Then, access the button you would like to style and assign the style to the button like this.
yourButton.Style = Application.Resources.Current["contactSqaure"] as Style;
You can also define styles in C#, or edit the button's dependency properties.
yourButton.Height = 160;
yourButton.VerticalAlignment = VerticalAlignment.Center;
you can access the button in your datatemplate when it loads.
<DataTemplate>
<Button Loaded="Button_Loaded" .../>
....
void Button_Loaded(object sender, RoutedEventArgs args)
{
(sender as Button).Style = yourStyle;
}

WPF access to control in template. Issue with CalendarDayButton

I'm trying to access at a Control in a Template. For this, I redefined control CalendarDayButton:
<Window.Resources>
<Style x:Key="myStyleDayButtonCalendar" TargetType="{x:Type CalendarDayButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CalendarDayButton}">
<Grid Name="gridCalendar">
<ContentControl Margin="5,1,5,1" Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Calendar CalendarDayButtonStyle="{StaticResource myStyleDayButtonCalendar}" Name="myCalendar" SelectedDatesChanged="Calendar_SelectedDatesChanged_1" />
</Grid>
It's OK for that. But when I want to access at my control GRID. Impossible:
private void Calendar_SelectedDatesChanged_1(object sender, SelectionChangedEventArgs e)
{
Grid gridInTemplate = (Grid)myCalendar.Template.FindName("gridCalendar", myCalendar) as Grid;
}
My grid is null. So, I tried with an other Control. With a Button:
<Window.Resources>
<Style x:Key="myStyleButton" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Name="myButton">
<Ellipse Fill="DarkBlue"></Ellipse>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Calendar CalendarDayButtonStyle="{StaticResource myStyleDayButtonCalendar}" Name="myCalendar" SelectedDatesChanged="Calendar_SelectedDatesChanged_1" />
<Button Style="{StaticResource myStyleButton}" Name="myButton2" Margin="92,99,518,338" Click="myButton2_Click_1"></Button>
</Grid>
Code behind:
private void myButton2_Click_1(object sender, RoutedEventArgs e)
{
Grid gridInTemplate = (Grid)myButton2.Template.FindName("myButton", myButton2);
}
And here gridInTemplate is NOT NULL. Why in the case dayCalendarButton gridInTemplate is NULL? I would like to avoid to use a VisualTreeHelper.
In your case, you're trying to find a CalendarDayButton in Calendar. But these are two different controls. For example, we have a style for the calendar:
<Style TargetType="{x:Type Calendar}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="BorderBrush" Value="Gray" />
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Calendar}">
<StackPanel x:Name="PART_Root" HorizontalAlignment="Center">
<CalendarItem x:Name="PART_CalendarItem" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In the code we can access to StackPanel like that:
private void Calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
StackPanel StackPanelInTemplate = (StackPanel)MyCalendar.Template.FindName("PART_Root", MyCalendar) as StackPanel;
MessageBox.Show(StackPanelInTemplate.Name);
}
But it is in the calendar style not exists CalendarDayButton. This a separate control. At the CalendarDayButton we can only get style:
Style MyCalendarDayButton = (Style)MyCalendar.CalendarDayButtonStyle;
But not a template. The construction of the form:
CalendarDayButton MyCalendarDayButton = FindChild<CalendarDayButton>(MyCalendar, "gridCalendar");
It will not work. Does not work for the reason that it is not in the same visual tree as the calendar.
Conclusion: maybe just access to CalendarDayButton will not work because it is not see in the visual tree of calendar (have designed so developers). Although I may be mistaken. I have a similar problem encountered when working with DatePicker. There can not be simply so to get access to some parts of DatePicker, for example: get access to Watermark - http://matthamilton.net/datepicker-watermark.
The decision depends on why you need it this button. Try to move your functionality in triggers like Control, Style or create your own control, inherited from the CalendarDayButton class (that tiresome). Our use converters, example:
<Grid x:Name="CalendarDayButtonGrid">
<Grid.ToolTip>
<MultiBinding Converter="{StaticResource HighlightDate}">
<MultiBinding.Bindings>
<Binding />
<Binding RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type Calendar}}" />
</MultiBinding.Bindings>
</MultiBinding>
</Grid.ToolTip>
<!-- End addition -->
</Grid>
P.S. Here you are told how to use WPF ToolKit access to CalendarDayButton: http://codesticks.wordpress.com/tag/wpf-toolkit/

Categories

Resources