I have designed some WPF CustomControl and I wrote a ControlTemplate for them, and i assigned ControlTemplates to those controls through styles :
for Example:
public class BootstrapText : TextBox
{
protected override void OnLostFocus(RoutedEventArgs e)
{
// ...
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
// ...
base.OnTextChanged(e);
}
public static readonly DependencyProperty RegexProperty = DependencyProperty.Register("Regex", typeof(string), typeof(BootstrapText), new PropertyMetadata(null));
public bool IsValid
{
// ...
}
public string Regex
{
// ...
}
static BootstrapText()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BootstrapText), new FrameworkPropertyMetadata(typeof(BootstrapText)));
}
}
and ControlTemplate (Actually Style) is Something like:
<Style TargetType="Controls:BootstrapText" BasedOn="{StaticResource FontBase}">
<Setter Property="Padding" Value="2"/>
<Setter Property="Width" Value="240"/>
<Setter Property="SnapsToDevicePixels" Value="True"></Setter>
<Setter Property="Background" Value="#FEFEFE"/>
<!--<Setter Property="VerticalContentAlignment" Value="Center"/>-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Controls:BootstrapText">
<Grid>
<Border Name="container" HorizontalAlignment="Left" Padding="{TemplateBinding Padding}" Height="{TemplateBinding Height}" BorderThickness="1" BorderBrush="#ccc" Background="{TemplateBinding Background}">
<ScrollViewer Padding="0" x:Name="PART_ContentHost" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" TextBlock.TextAlignment="Center" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
While i have no x:Key="SomeKey" in the style. this style will affects all BootstrapTexts.
This is Good. But I have another problem. Somewhere in a Form, I want to Style these Controls and Set some Margin and Padding for them but leave the defaults as they are. but my Control will be disappeared !!!
I think twice styling will hides the default style.
In the standard Controls like TextBox, Button, ... whenever we write some style, everything is good and style simply sets our properties.
I think, I must assign my ControlTemplate directly to the CustomControl Because i must get ride of this Styling process. but I don't know how ?
A default Style for a control should be define in a ResourceDictionary called generic.xaml and this ResourceDictionary should be located in a folder called Themes at the root of the assembly in which the control is defined by default.
These names are by convention so just move your Style to Themes/generic.xaml.
Related
I have a class assembly that contains a number of user controls. I want to add a new type of button to this class assembly and I also want to add styling to it. Specifically I want a button that contains multiple images for use in Normal, Hover (IsMouseOver) and disabled states. It will also contain text to display. Then I could add this in any application using this class assembly as follows:
<ns:ImageTextButton NormalImage="{StaticResource SomeImage}" HoverImage="{StaticResource SomeHoverImage}" Text={StaticResource SomeText}" />
First I created a C# class:
public class ImageTextButton : Button {
public DependencyProperty ImageProperty = DependencyProperty.Register(nameof(Image), typeof(DrawingBrush), typeof(ImageTextButton));
public DependencyProperty HoverImageProperty = DependencyProperty.Register(nameof(HoverImage), typeof(DrawingBrush), typeof(ImageTextButton));
public DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(ImageTextButton));
public DrawingBrush Image {
get { return GetValue(ImageProperty) as DrawingBrush; }
set { SetValue(ImageProperty, value); }
}
public DrawingBrush HoverImage {
get { return GetValue(HoverImageProperty) as DrawingBrush; }
set { SetValue(HoverImageProperty, value); }
}
public string Text {
get { return GetValue(TextProperty) as string; }
set { SetValue(TextProperty, value); }
}
}
Then I created a style in a Styles.xaml file which is compiled as a Resource.
<Style TargetType="{x:Type local:ImageTextButton}" x:Key="ImageTextButtonStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="0" Background="{Binding RelativeSource={RelativeSource AncestorType=local:ImageTextButton}, Path=Image}">
<StackPanel>
<Canvas x:Name="canvas" Background="{Binding RelativeSource={RelativeSource AncestorType=local:ImageTextButton}, Path=Image}" />
<TextBlock Style="{Binding RelativeSource={RelativeSource AncestorType=local:ImageTextButton}, Path=TextStyle}"
Text="{Binding RelativeSource={RelativeSource AncestorType=local:ImageTextButton}, Path=Text}" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=HoverImage}" TargetName="canvas" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have not been able to test this style and there very well may be issues in it. I can't get the application to start because the TargetType above doesn't exist. I believe this is because my Styles.xaml is a resource and the actual class is compiled. The error I get is Failed to create a 'Type' from the text 'local:ImageTextButton'.
How can I achieve this? Secondly, is there any way to apply this style by default to this type? I don't want to always have to specify Style={StaticResource ImageTextButtonStyle} in each instance of this user item.
First you have to decide if you are going to build an UserControl or a CustomControl.
UserControl needs to be derived from "UserControl" Base class. As your C# code extends "Button", that implementation doesn't fall under "UserControl".
For a CustomControl, your Style should be present in a file named "Generic.xaml" which SHOULD BE PLACED in a folder "Themes". (You can still change the default Themes Folder location). And your C# file should define the Key which will be used to find/target/identify the Xaml Style.
Your above setup should not work as it satisfies neither Usercontrol nor Custom Control Requirements.
I cannot create a full example but can direct you to a certain extent.
You need to have the below Static Method to set your default style as a bare minimum. Add remaining properties as required.
public class ImageButton : Button
{
static ImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
}
public ImageButton()
{
}
For the Xaml Part. Create a Folder Themes and inside that create a resource dictionary by name "Generic.xaml". Inside your Generic.Xaml, add your styles. (Place your xaml logic inside the control template).
<Style TargetType="{x:Type bc:ImageButton}">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type bc:ImageButton}">
</ControlTemplate>
</Setter>
</Style>
All the above are bare minimum requirement for a CUSTOM CONTROL. In case you wish to create a UserControl, you need a .xaml & a .xaml.cs files. You can easily use visual studio context menu (rightclick on solution) and create a usercontrol.
Important: When you are creating a custom control, you dont need something like below
Background="{Binding RelativeSource={RelativeSource AncestorType=local:ImageTextButton}, Path=Image}">
Just use, TemplateBinding
Background="{TemplateBinding Image}">
The RelativeSource Mode FindAncestor is missing
change {RelativeSource AncestorType=local:ImageTextButton} to {RelativeSource FindAncestor, AncestorType=local:ImageTextButton}
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.
I'm having an issue getting the following to work. I have created a subclass of button with several DependencyProperties. and we are attempting to use these in the style.
We have the following c#
/// <summary>
/// Custom Is Mouse Over Colour.
/// </summary>
public static DependencyProperty LITIsMouseOverProperty =
DependencyProperty.RegisterAttached("LITIsMouseOver",
typeof(System.Windows.Media.LinearGradientBrush), typeof(Button));
public static System.Windows.Media.LinearGradientBrush GetLITIsMouseOver(DependencyObject target)
{
return (System.Windows.Media.LinearGradientBrush)target.GetValue(LITIsMouseOverProperty);
}
public static void SetLITIsMouseOver(DependencyObject target, System.Windows.Media.LinearGradientBrush value)
{
target.SetValue(LITIsMouseOverProperty, value);
}
and the following XAML:
<Style x:Key="StandardButton" TargetType="{x:Type Utils:LITCustomButton01}">
<Setter Property="LITIsMouseOver" Value="{StaticResource DarkBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Utils:LITCustomButton01}">
<Border x:Name="buttonBorder" CornerRadius="{TemplateBinding LITCornerRadius}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}">
<TextBlock Name="textBlock" Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{TemplateBinding Foreground}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="{TemplateBinding LITIsMouseOver}" />
</Trigger>
</Style.Triggers>
</Style>
We have this same system working on corner radius, using the following:
/// <summary>
/// Custom Corner Radius
/// </summary>
public static DependencyProperty LITCornerRadiusProperty =
DependencyProperty.RegisterAttached("LITCornerRadius",
typeof(CornerRadius), typeof(Button));
public static CornerRadius GetLITCornerRadius(DependencyObject target)
{
return (CornerRadius)target.GetValue(LITCornerRadiusProperty);
}
public static void SetLITCornerRadius(DependencyObject target, CornerRadius value)
{
target.SetValue(LITCornerRadiusProperty, value);
}
However, the background throws the following error:
"member is not valid because it does not have a qualifying type name."
TemplateBinding can only be used in a ControlTemplate. The TemplateBinding shortcut does not work in triggers so you will have to use the RelativeSource binding. When overriding the ControlTemplate you will need to specify the target of the trigger. Something like this:
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Background"
TargetName="buttonBorder"
Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LITIsMouseOver}"/>
</Trigger>
</ControlTemplate.Triggers>
Also, you are creating an attached property which would allow you to do:
<Grid Utils:LITCustomButton01.LITIsMouseOver="Pink"/>
In this case, nothing will happen on MouseOver. You want to use Register as opposed to RegisterAttached.
I trying to create a custom textbox control in windows 8 XAML.
I've right clicked on my project -> Add -> New Item
I've then selected Templated Control and entered the name MyTextBox
I have then made this class derive from TextBox and added a test Method called Hello. So it now looks like this:
public sealed class MyTextBox : TextBox
{
public MyTextBox()
{
this.DefaultStyleKey = typeof(MyTextBox);
}
public void Hello()
{
//Do something here!
}
}
Within my project a file has also been added called Generic.xaml with the following style:
<Style TargetType="local:MyTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyTextBox">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So within here I tried adding BasedOn="TextBox" with the Style tag:
<Style TargetType="local:WatermarkTextBox" BasedOn="TextBox">
This doesn't work.
What do I need to do to create this custom TextBox and then how do I use it within my XAML
This is for a Windows RT so the XAML maybe different from WPF.
One of best article and my favorite : Building a deployable custom control for XAML Metro style apps
Here is MSDN sample app : XAML user and custom controls sample
UPDATE 1 :
<Style TargetType="local:WatermarkTextBox" BasedOn="TextBox">
You don't have to specify BasedOn attribute. If you are developing watermark textbox then I would recommend you to check Callisto's watermark textbox code.
Generic.xaml
WatermarkTextBox.cs
BasedOn values can be pointed through StaticResources like BasedOn={StaticResource DefaultTextBoxStyle}
I have a NumericOnlyTextBox implementation. Instead of making it BasedOn textbox I've added a textbox and all its dependencies.
In example:
<Style TargetType="local:NumericOnlyTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NumericOnlyTextBox">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<TextBox x:Name="TextBoxPart"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EnteredText, Mode=TwoWay}"
MaxLength="{TemplateBinding MaxLength}">
</TextBox>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsTabStop" Value="False"></Setter>
</Style>
I am having a tricky problem, I want some slightly unusual behaviour from a checkbox and can't seem to figure it out. Any suggestions would be most welcome. The behaviour I want is:
The CheckBox is enabled and ready for the user to click, IsChecked represents a bound boolean value stored in a data structure
The user clicks the CheckBox causing the click event to fire but the bound value in the data structure is NOT updated and the visual representation of the CheckBox is NOT updated but it is disabled to stop further clicking
The click event triggers a message to be sent to a remote device which takes some time to respond
The remote device responds causing the data structure to be updated with the new value, the binding then updates the isChecked status and the CheckBox gets reenabled for further clicking
The problem I have is that although a OneWay data binding works at not updating the data structure when the CheckBox is clicked, the visual representation does change (which I think is odd, shouldn't IsChecked now act like a pointer to the value in the data structure).
I can reverse the change in the Click() event and do the disable there as well but this is pretty messy. I can also have the set property of the data structure value to set an isEnabled value which is also bound to reenable the CheckBox but that seems messy too.
Is there a clean way to do this? Perhaps with a derived CheckBox class? How can I stop the visual representation getting updated?
Thanks
Ed
What about data binding to the IsHitTestVisible property?
For example, assuming an MVVM approach:
Add a IsReadOnly property to your view model, initially set as true to allow click.
Binding this property to CheckBox.IsHitTestVisible.
After the first click, update your view model to set this value to false, preventing any further clicks.
I don't have this exact requirement, I just needed an always read only checkbox, and it seems to solve the problem nicely. Also note Goran's comment below about the Focusable property.
This answer is not your question, but it answers the question from the title.
Checkbox in WPF does not have the IsReadOnly property.
But, similar behavior is achieved using properties
IsHitTestVisible="False" and Focusable="False"
<CheckBox IsHitTestVisible="False"
Focusable="False"/>
I don't think that creating a whole control for this is necessary.
The issue that you're running into comes from the fact that the place where you see 'the check' isn't really the checkbox, it's a bullet. If we look at the ControlTemplate for a CheckBox we can see how that happens (Though I like the Blend template better). As a part of that, even though your binding on the IsChecked property is set to OneWay it is still being updated in the UI, even if it is not setting the binding value.
As such, a really simple way to fix this, is to just modify the ControlTemplate for the checkbox in question.
If we use Blend to grab the control template we can see the Bullet inside the ControlTemplate that represents the actual checkbox area.
<BulletDecorator SnapsToDevicePixels="true"
Background="Transparent">
<BulletDecorator.Bullet>
<Microsoft_Windows_Themes:BulletChrome Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
IsChecked="{TemplateBinding IsChecked}"
RenderMouseOver="{TemplateBinding IsMouseOver}"
RenderPressed="{TemplateBinding IsPressed}" />
</BulletDecorator.Bullet>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True" />
</BulletDecorator>
In here, the IsChecked and RenderPressed are what are actually making the 'Check' appear, so to fix it, we can remove the binding from the IsChecked property on the ComboBox and use it to replace the TemplateBinding on the IsChecked property of the Bullet.
Here's a small sample demonstrating the desired effect, do note that to maintain the Vista CheckBox look the PresentationFramework.Aero dll needs to be added to the project.
<Window x:Class="Sample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
Title="Window1"
Height="300"
Width="300">
<Window.Resources>
<SolidColorBrush x:Key="CheckBoxFillNormal"
Color="#F4F4F4" />
<SolidColorBrush x:Key="CheckBoxStroke"
Color="#8E8F8F" />
<Style x:Key="EmptyCheckBoxFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle SnapsToDevicePixels="true"
Margin="1"
Stroke="Black"
StrokeDashArray="1 2"
StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CheckRadioFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle SnapsToDevicePixels="true"
Margin="14,0,0,0"
Stroke="Black"
StrokeDashArray="1 2"
StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CheckBoxStyle1"
TargetType="{x:Type CheckBox}">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="Background"
Value="{StaticResource CheckBoxFillNormal}" />
<Setter Property="BorderBrush"
Value="{StaticResource CheckBoxStroke}" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="FocusVisualStyle"
Value="{StaticResource EmptyCheckBoxFocusVisual}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<BulletDecorator SnapsToDevicePixels="true"
Background="Transparent">
<BulletDecorator.Bullet>
<Microsoft_Windows_Themes:BulletChrome Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
RenderMouseOver="{TemplateBinding IsMouseOver}" />
</BulletDecorator.Bullet>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True" />
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="HasContent"
Value="true">
<Setter Property="FocusVisualStyle"
Value="{StaticResource CheckRadioFocusVisual}" />
<Setter Property="Padding"
Value="4,0,0,0" />
</Trigger>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<CheckBox x:Name="uiComboBox"
Content="Does not set the backing property, but responds to it.">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<BulletDecorator SnapsToDevicePixels="true"
Background="Transparent">
<BulletDecorator.Bullet>
<Microsoft_Windows_Themes:BulletChrome Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
RenderMouseOver="{TemplateBinding IsMouseOver}"
IsChecked="{Binding MyBoolean}">
</Microsoft_Windows_Themes:BulletChrome>
</BulletDecorator.Bullet>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True" />
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="HasContent"
Value="true">
<Setter Property="FocusVisualStyle"
Value="{StaticResource CheckRadioFocusVisual}" />
<Setter Property="Padding"
Value="4,0,0,0" />
</Trigger>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<TextBlock Text="{Binding MyBoolean, StringFormat=Backing property:{0}}" />
<CheckBox IsChecked="{Binding MyBoolean}"
Content="Sets the backing property." />
</StackPanel>
</Grid>
</Window>
And the code behind, with our backing Boolean value:
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private bool myBoolean;
public bool MyBoolean
{
get
{
return this.myBoolean;
}
set
{
this.myBoolean = value;
this.NotifyPropertyChanged("MyBoolean");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
I had a need for the AutoCheck functionality be off as well for a Checkbox/RadioButton, where I wanted to handle the Click event without having the control auto-check. I've tried various solutions here and on other threads and was unhappy with the results.
So I dug into what WPF is doing (using Reflection), and I noticed:
Both CheckBox & RadioButton inherit from the ToggleButton control primitive. Neither of them have a OnClick function.
The ToggleButton inherits from the ButtonBase control primitive.
The ToggleButton overrides the OnClick function and does: this.OnToggle(); base.OnClick();
ButtonBase.OnClick does 'base.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, this));'
So basically, all I needed to do was override the OnClick event, don't call OnToggle, and do base.RaiseEvent
Here's the complete code (note that this can easily be reworked to do RadioButtons as well):
using System.Windows.Controls.Primitives;
public class AutoCheckBox : CheckBox
{
private bool _autoCheck = false;
public bool AutoCheck {
get { return _autoCheck; }
set { _autoCheck = value; }
}
protected override void OnClick()
{
if (_autoCheck) {
base.OnClick();
} else {
base.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, this));
}
}
}
I now have a CheckBox that doesn't auto-check, and still fires the Click event. Plus I can still subscribe to the Checked/Unchecked events and handle things there when I programmatically change the IsChecked property.
One final note: unlike other solutions that do something like IsChecked != IsChecked in a Click event, this won't cause the Checked/Unchecked/Indeterminate events to fire until you programmatically set the IsChecked property.
This is the class I've written to do something similar, for similar reasons (still raises all the Click and Command events as normal, but does not alter the binding source by default and does not auto-toggle. Unfortunately it does still have the animated fade-in-out on click, which is a bit strange if the click-handling code doesn't end up changing IsChecked.
public class OneWayCheckBox : CheckBox
{
private class CancelTwoWayMetadata : FrameworkPropertyMetadata
{
protected override void Merge(PropertyMetadata baseMetadata,
DependencyProperty dp)
{
base.Merge(baseMetadata, dp);
BindsTwoWayByDefault = false;
}
}
static OneWayCheckBox()
{
// Remove BindsTwoWayByDefault
IsCheckedProperty.OverrideMetadata(typeof(OneWayCheckBox),
new CancelTwoWayMetadata());
}
protected override void OnToggle()
{
// Do nothing.
}
}
Usage:
<yourns:OneWayCheckBox IsChecked="{Binding SomeValue}"
Command="{x:Static yourns:YourApp.YourCommand}"
Content="Click me!" />
(Note that the IsChecked binding is now one-way by default; you can declare it as TwoWay if you want, but that would defeat part of the point.)
Late answer, but I just came across the question looking for something else.
What you want is not a checkbox with unusual behaviour at all, what you want is a button with unusual appearance. It's always easier to change the appearance of a control than its behaviour.
Something along these lines ought to do (untested)
<Button Command="{Binding CommandThatStartsTask}">
<Button.Template>
<ControlTemplate>
<CheckBox IsChecked="{Binding PropertySetByTask, Mode=OneWay}" />
</ControlTemplate>
</Button.Template>
</Button>
Use Validation to block the boolean from getting toggled when you don't want it to - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx
This is much less scary than the other answer, or hooking Clicked
I've been trying to create my generic ReadOnlyCheckBox style/template but I'm having a problem with the binding to the data. In the example you bind directly to the data from the ControlTemplate definition, but of course this is not really what I want, as I want to be able to declare the new checkbox something like this:
<CheckBox x:Name="uiComboBox" Content="Does not set the backing property, but responds to it."
Style="{StaticResource ReadOnlyCheckBoxStyle}" IsChecked="{Binding MyBoolean}" Click="uiComboBox_Click"/>
Except of course when I do this and then set the event trigger on the bullet to be a TemplateBinding of IsChecked I have exactly what I started with! I guess I don't understand why setting the binding directly in the bullet is different from setting IsChecked and then binding to that, isn't the TemplateBinding just a way of referencing what is set in the properties of the control being created? How is the Click triggering the UI update even tho the data does not get updated? Is there a trigger for Click I can override to stop the update?
I got all the DictionaryResource stuff working fine so I am happy with that, cheers for the pointer.
The other thing I was curious about was if it is possible to reduce my Control/Style template by using the BasedOn parameter in the style, then I would only override the things I actually need to change rather than declaring a lot of stuff that I think is part of the standard template anyway. I might have a play with this.
Cheers
ed
If it helps anyone, a fast and elegant solution I've found is to hook with the Checked and Unchecked events and manipulate the value based on your flag.
public bool readOnly = false;
private bool lastValidValue = false;
public MyConstructor()
{
InitializeComponent();
contentCheckBox.Checked += new
RoutedEventHandler(contentCheckBox_Checked);
contentCheckBox.Unchecked += new
RoutedEventHandler(contentCheckBox_Checked);
}
void contentCheckBox_Checked(object sender, RoutedEventArgs e)
{
if (readOnly)
contentCheckBox.IsChecked = lastValidValue;
else
lastValidValue = (bool)contentCheckBox.IsChecked;
}
Wrap the CheckBox in a ContentControl. Make the ContentControl IsEnabled=False
Those two Properties are – IsHitTestVisible and Focusable
Make thse two properties to False. This makes the readonly checkbox in WPF.
So final XAML statement will be as follows for readonly checkbox in WPF –