Button using dependency properties for a separate image per state - c#

I have a Button control that I want to be able to reuse throughout project. Each time button enters a new state, a different image will be displayed. For now, I have Normal State and Pressed State.
Here's the XAML portion of the control:
<Button
x:Class="customImageButton.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<ContentControl Width="80">
<Grid>
<Image Name="Normal" Source="{Binding NormalState}"/>
<Image Name="Pressed" Source="{Binding PressedState}" Visibility="Hidden"/>
</Grid>
</ContentControl>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Normal" Property="Visibility" Value="Hidden"/>
<Setter TargetName="Pressed" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
Here's the code-behind for the control:
namespace customImageButton
{
public partial class ImageButton : Button
{
public ImageButton()
{
this.InitializeComponent();
}
public ImageSource NormalState
{
get { return base.GetValue(NormalStateProperty) as ImageSource; }
set { base.SetValue(NormalStateProperty, value); }
}
public static readonly DependencyProperty NormalStateProperty =
DependencyProperty.Register("NormalState", typeof(ImageSource), typeof(ImageButton));
public ImageSource PressedState
{
get { return base.GetValue(PressedStateProperty) as ImageSource; }
set { base.SetValue(PressedStateProperty, value); }
}
public static readonly DependencyProperty PressedStateProperty =
DependencyProperty.Register("PressedState", typeof(ImageSource), typeof(ImageButton));
}
}
...and here is its use:
<local:ImageButton Content="CustomButton" HorizontalAlignment="Left"
VerticalAlignment="Top" NormalState="Resources/Normal.png"
PressedState="Resources/Pressed.png"/>
My problem is that the images I've provided are not displaying. The Build Action for both images is Resource and I have tried using absolute path; however, that provided the same result. What am I missing?

Two problems with the code as listed:
The bindings need to be TemplateBinding.
The TargetType should refer to the "ImageButton" type, not Button.
Like this:
<ControlTemplate
xmlns:local="clr-namespace:customImageButton"
TargetType="{x:Type local:ImageButton}">
<Grid>
<ContentControl Width="80">
<Grid>
<Image Name="Normal" Source="{TemplateBinding NormalState}"/>
<Image Name="Pressed" Source="{TemplateBinding PressedState}" Visibility="Hidden"/>
</Grid>
</ContentControl>
</Grid>
Note: The above builds and runs, but Visual Studio complains:
'ImageButton' ControlTemplate TargetType does not match templated type 'Button'.
I suggest putting the control template in its own Style in the Themes/Generic.xaml resource dictionary, rather than inline.

Related

Hit-Testing in WPF for irregularly-shaped items

I have an irregularly shaped item (a line shape) contained within a ContentControl-derived class ("ShapeItem"). I style it with a custom cursor and I handle mouse-clicks within the ShapeItem class.
Unfortunately WPF thinks that the mouse is "over" my item if it is anywhere within the rectangular bounding box of the ContentControl. That's OK for closed shapes like a rect or circle, but it's a problem for a diagonal line. Consider this image with 3 such shapes on display and their bounding boxes shown in white:
Even if I am in the very bottom left hand corner of the bounding box around the line, it still shows the cursor and the mouse clicks still reach my custom item.
I want to change this so that that the mouse is only considered to be "over" the line detected if I am within a certain distance of it. Like, this region in red (forgive the crude drawing).
My question is, how do I approach this? Do I override some virtual "HitTest" related function on my ShapeItem?
I already know the math to figure out if I'm in the right place. I'm just wondering what approach is the best to choose. What functions do I override? Or what events do I handle, etc. I've gotten lost in the WPF documentation on Hit testing. Is it a matter of overriding HitTestCore or something like that?
Now for code. I host the items in a custom ItemsControl called "ShapesControl".
which uses the custom "ShapeItem" container to to host my view-model objects :
<Canvas x:Name="Scene" HorizontalAlignment="Left" VerticalAlignment="Top">
<gcs:ShapesControl x:Name="ShapesControl" Canvas.Left="0" Canvas.Top="0"
ItemsSource="{Binding Shapes}">
<gcs:ShapesControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" IsItemsHost="True" />
</ItemsPanelTemplate>
</gcs:ShapesControl.ItemsPanel>
<gcs:ShapesControl.ItemTemplate>
<DataTemplate DataType="{x:Type gcs:ShapeVm}">
<Path ClipToBounds="False"
Data="{Binding RelativeGeometry}"
Fill="Transparent"/>
</DataTemplate>
</gcs:ShapesControl.ItemTemplate>
<!-- Style the "ShapeItem" container that the ShapesControl wraps each ShapeVm ine -->
<gcs:ShapesControl.ShapeItemStyle>
<Style TargetType="{x:Type gcs:ShapeItem}"
d:DataContext="{d:DesignInstance {x:Type gcs:ShapeVm}}"
>
<!-- Use a custom cursor -->
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Cursor" Value="SizeAll"/>
<Setter Property="Canvas.Left" Value="{Binding Path=Left, Mode=OneWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Top, Mode=OneWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type gcs:ShapeItem}">
<Grid SnapsToDevicePixels="True" Background="{TemplateBinding Panel.Background}">
<!-- First draw the item (i.e. the ShapeVm) -->
<ContentPresenter x:Name="PART_Shape"
Content="{TemplateBinding ContentControl.Content}"
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentControl.ContentTemplateSelector}"
ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}"
HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
IsHitTestVisible="False"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"
RenderTransformOrigin="{TemplateBinding ContentControl.RenderTransformOrigin}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</gcs:ShapesControl.ShapeItemStyle>
</gcs:ShapesControl>
</Canvas>
My "ShapesControl"
public class ShapesControl : ItemsControl
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is ShapeItem);
}
protected override DependencyObject GetContainerForItemOverride()
{
// Each item we display is wrapped in our own container: ShapeItem
// This override is how we enable that.
// Make sure that the new item gets any ItemTemplate or
// ItemTemplateSelector that might have been set on this ShapesControl.
return new ShapeItem
{
ContentTemplate = this.ItemTemplate,
ContentTemplateSelector = this.ItemTemplateSelector,
};
}
}
And my "ShapeItem"
/// <summary>
/// A ShapeItem is a ContentControl wrapper used by the ShapesControl to
/// manage the underlying ShapeVm. It is like the the item types used by
/// other ItemControls, including ListBox, ItemsControls, etc.
/// </summary>
[TemplatePart(Name="PART_Shape", Type=typeof(ContentPresenter))]
public class ShapeItem : ContentControl
{
private ShapeVm Shape => DataContext as ShapeVm;
static ShapeItem()
{
DefaultStyleKeyProperty.OverrideMetadata
(typeof(ShapeItem),
new FrameworkPropertyMetadata(typeof(ShapeItem)));
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
// Toggle selection when the left mouse button is hit
base.OnMouseLeftButtonDown(e);
ShapeVm.IsSelected = !ShapeVm.IsSelected;
e.Handled = true;
}
internal ShapesControl ParentSelector =>
ItemsControl.ItemsControlFromItemContainer(this) as ShapesControl;
}
The "ShapeVm" is just an abstract base class for my view models. Roughly this:
public abstract class ShapeVm : BaseVm, IShape
{
public virtual Geometry RelativeGeometry { get; }
public bool IsSelected { get; set; }
public double Top { get; set; }
public double Left { get; set; }
public double Width { get; }
public double Height { get; }
}
You could use a ShapeItem class like shown below. It is a Canvas with two Path children, one for hit testing and one for display. It resembles a few of the typical Shape properties (which you may extend according to your needs).
public class ShapeItem : Canvas
{
public ShapeItem()
{
var path = new Path
{
Stroke = Brushes.Transparent,
Fill = Brushes.Transparent
};
path.SetBinding(Path.DataProperty,
new Binding(nameof(Data)) { Source = this });
path.SetBinding(Shape.StrokeThicknessProperty,
new Binding(nameof(HitTestStrokeThickness)) { Source = this });
Children.Add(path);
path = new Path();
path.SetBinding(Path.DataProperty,
new Binding(nameof(Data)) { Source = this });
path.SetBinding(Shape.FillProperty,
new Binding(nameof(Fill)) { Source = this });
path.SetBinding(Shape.StrokeProperty,
new Binding(nameof(Stroke)) { Source = this });
path.SetBinding(Shape.StrokeThicknessProperty,
new Binding(nameof(StrokeThickness)) { Source = this });
Children.Add(path);
}
public static readonly DependencyProperty DataProperty =
Path.DataProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty FillProperty =
Shape.FillProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty StrokeProperty =
Shape.StrokeProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty StrokeThicknessProperty =
Shape.StrokeThicknessProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty HitTestStrokeThicknessProperty =
DependencyProperty.Register(nameof(HitTestStrokeThickness), typeof(double), typeof(ShapeItem));
public Geometry Data
{
get => (Geometry)GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
public Brush Fill
{
get => (Brush)GetValue(FillProperty);
set => SetValue(FillProperty, value);
}
public Brush Stroke
{
get => (Brush)GetValue(StrokeProperty);
set => SetValue(StrokeProperty, value);
}
public double StrokeThickness
{
get => (double)GetValue(StrokeThicknessProperty);
set => SetValue(StrokeThicknessProperty, value);
}
public double HitTestStrokeThickness
{
get => (double)GetValue(HitTestStrokeThicknessProperty);
set => SetValue(HitTestStrokeThicknessProperty, value);
}
}
public class ShapeItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ShapeItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is ShapeItem;
}
}
You would use it an XAML like this:
<gcs:ShapeItemsControl ItemsSource="{Binding Shapes}">
<gcs:ShapeItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</gcs:ShapeItemsControl.ItemsPanel>
<gcs:ShapeItemsControl.ItemContainerStyle>
<Style TargetType="gcs:ShapeItem">
<Setter Property="Data" Value="{Binding RelativeGeometry}"/>
<Setter Property="Fill" Value="AliceBlue"/>
<Setter Property="Stroke" Value="Yellow"/>
<Setter Property="StrokeThickness" Value="3"/>
<Setter Property="HitTestStrokeThickness" Value="15"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</gcs:ShapeItemsControl.ItemContainerStyle>
</gcs:ShapeItemsControl>
However, you may not need a ShapeItem class and a derived ItemsControl at all, when you put the Canvas in the ItemTemplate of a regular ItemsControl:
<ItemsControl ItemsSource="{Binding Shapes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Cursor="Hand">
<Path Data="{Binding RelativeGeometry}" Fill="Transparent"
Stroke="Transparent" StrokeThickness="15"/>
<Path Data="{Binding RelativeGeometry}" Fill="AliceBlue"
Stroke="Yellow" StrokeThickness="3"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you also need to support selection, you should use a ListBox instead of an ItemsControl. A third Path in the ItemTemplate could visualize the selection state.
<ListBox ItemsSource="{Binding Shapes}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Template>
<ControlTemplate TargetType="ListBox">
<ItemsPresenter/>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Canvas Cursor="Hand">
<Path Data="{Binding RelativeGeometry}" Fill="Transparent"
Stroke="Transparent" StrokeThickness="15"/>
<Path Data="{Binding RelativeGeometry}"
Stroke="Green" StrokeThickness="7"
StrokeStartLineCap="Square" StrokeEndLineCap="Square"
Visibility="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListBoxItem},
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Path Data="{Binding RelativeGeometry}" Fill="AliceBlue"
Stroke="Yellow" StrokeThickness="3"/>
</Canvas>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Fired datatrigger is not changing custom control's property(ControlTemplate)

I Want to change a DependcyProperty(Named is as MessageTemplateProperty) which is in CustomControl and Type is Controltemplate .
But I can't set the value to it using datatrigger
But another DependcyProperty(named as IsShowMessageProperty) which is in CustomControl and Type is bool can be set using datatrigger.
Who can explain it ,Why, and How to solve it.
The Custom Code as follow:
public class MessageOverLay : ContentControl
{
static MessageOverLay()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MessageOverLay),
new FrameworkPropertyMetadata(typeof(MessageOverLay)));
}
#region DependcyProperties
// Template attached property
public static readonly DependencyProperty MessageTemplateProperty =
DependencyProperty.Register("MessageTemplate", typeof(ControlTemplate), typeof(MessageOverLay),
new FrameworkPropertyMetadata(MessageTemplateChanged));
public ControlTemplate MessageTemplate
{
set{SetValue(MessageTemplateProperty, value);}
get{return (ControlTemplate)GetValue(MessageTemplateProperty);}
}
// IsVisible attached property
public static readonly DependencyProperty IsShowMessageProperty =
DependencyProperty.Register("IsShowMessage", typeof(bool), typeof(MessageOverLay),
new PropertyMetadata(IsShowMessageChanged));
public bool IsShowMessage
{
get{return (bool)GetValue(IsShowMessageProperty);}
set{SetValue(IsShowMessageProperty, value);}
}
#endregion DependcyProperties
}
The Custom Default Theme Generic.xaml
<Style TargetType="{x:Type controls:MessageOverLay}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:MessageOverLay">
<AdornerDecorator>
<Grid>
<ContentPresenter x:Name="PART_Conent"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Content="{TemplateBinding Content}" />
<Control x:Name="Part_MessageControl" Template="{TemplateBinding MessageTemplate}"
Visibility="{TemplateBinding IsShowMessage,Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</AdornerDecorator>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="MessageTemplate">
<Setter.Value>
<ControlTemplate>
<Grid HorizontalAlignment="Left" VerticalAlignment="Bottom">
<Grid Width="150" Height="100" Margin="5 0 0 10">
<Rectangle Stroke="Black" Fill="Yellow" RadiusX="6" RadiusY="6" Margin="0 20 0 0" />
<TextBlock Text="What are you doing?" Margin="5 25 0 0" />
<Button Content="Cancel" Margin="5" VerticalAlignment="Bottom" HorizontalAlignment="Right" />
<Button Content="OK" Margin="5" VerticalAlignment="Bottom" HorizontalAlignment="Left" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I Want to use it as follow:
Demo XAML:
<Window ...>
<Window.Resources>
<ControlTemplate x:Key="GenderPopupTemplate">
<Grid HorizontalAlignment="Left" VerticalAlignment="Bottom">
<Grid Width="200" Height="100" Margin=" 5 0 0 10">
<TextBlock Text="Please Select Gender " Margin="5 25 0 0" />
<Button Content="Male" Margin="5" VerticalAlignment="Bottom" HorizontalAlignment="Left"
Command="{Binding SelectedGenderCommand}" />
<Button Content="FeMale" Margin="5" VerticalAlignment="Bottom" HorizontalAlignment="Right"
Command="{Binding SelectedGenderCommand}" />
</Grid>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="FacePopupTemplate">
<Grid HorizontalAlignment="Left" VerticalAlignment="Bottom">
<Grid Width="200" Height="100" Margin="5 10 0 0">
<TextBlock Text="Do you like your Face,Now?" Margin="5 25 0 0" />
<Button Content="OK" Margin="5" VerticalAlignment="Bottom" HorizontalAlignment="Left"
Command="{Binding OKCommand}" />
</Grid>
</Grid>
</ControlTemplate>
</Window.Resources>
<controls:MessageOverLay HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MessageTemplate="{StaticResource GenderPopupTemplate}"
IsShowMessage="True">
<controls:MessageOverLay.Style>
<Style TargetType="{x:Type controls:MessageOverLay}">
<Style.Triggers>
<DataTrigger Binding="{Binding MessageBoxType}" Value="{x:Static viewModels:MessageBoxTypes.SelectView}">
<Setter Property="MessageTemplate" Value="{StaticResource GenderPopupTemplate}"></Setter>
<Setter Property="Height" Value="350"/>
</DataTrigger>
<DataTrigger Binding="{Binding MessageBoxType}" Value="{x:Static viewModels:MessageBoxTypes.MessageView}">
<Setter Property="MessageTemplate" Value="{StaticResource FacePopupTemplate}"></Setter>
<Setter Property="Height" Value="150"/>
</DataTrigger>
</Style.Triggers>
</Style>
</controls:MessageOverLay.Style>
<Grid Background="SeaGreen"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock Text="Content"/>
</Grid>
</controls:MessageOverLay>
I want to change the MessageTemplate when the Propery MessageBoxType of MainWindowViewModel changed.
But I can't archive it.
The other related code
Demo C# Code:
public class MainWindowViewModel : BindableBase
{
public ICommand SelectedGenderCommand { get; }
public ICommand OKCommand { get; }
private MessageBoxTypes _messageBoxType;
public MessageBoxTypes MessageBoxType
{
get { return _messageBoxType; }
set { SetProperty(ref _messageBoxType, value); }
}
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public MainWindowViewModel()
{
MessageBoxType = MessageBoxTypes.SelectView;
SelectedGenderCommand = new DelegateCommand<object>(SelectedGender);
OKCommand = new DelegateCommand<object>(OK);
}
private void OK(object obj)
{
MessageBoxType = MessageBoxTypes.SelectView;
}
private void SelectedGender(object obj)
{
MessageBoxType = MessageBoxTypes.MessageView;
}
}
public enum MessageBoxTypes
{
SelectView,
MessageView,
ConfirmView
}
Update:
Here is the full demo code in github ,Please check it.
When a dependency property is to be set by a Style Setter, there must be no direct assignment of a so-called local value, as you do in
<controls:MessageOverLay MessageTemplate="{StaticResource GenderPopupTemplate}" ...>
The directly assigned local value always has higher precedence than any value from Style Setters (and other possible sources), so that the Setter has no effect. More details can be found here: Dependency Property Value Precedence.
Replace the direct assignment be another Style Setter:
<controls:MessageOverLay ...>
<controls:MessageOverLay.Style>
<Style TargetType="{x:Type controls:MessageOverLay}">
<Setter Property="MessageTemplate"
Value="{StaticResource GenderPopupTemplate}"/>
<Style.Triggers>
...
</Style.Triggers>
</Style>
</controls:MessageOverLay.Style>
...
</controls:MessageOverLay>

WPF Databinding DataTrigger to change color of shape based on boolean value

I am trying to change the color of a shape by means of databinding and data trigger.
But i am still new to WPF and all.
Let me illustrate with an example. this is a group box
<GroupBox x:Class="Server.Host.SingleAxisControls"
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:host="clr-namespace:Server.Host"
mc:Ignorable="d"
d:DesignWidth="200">
<Grid>
<StackPanel Orientation="Vertical" Width="180" >
<host:MyRectangleControl x:Name="MyRectangle" />
<Button Click="OnButton_Click" Width="80" Margin="20,5,20,5">On</Button>
<Button Click="OffButton_Click" Width="80">Off</Button>
</StackPanel>
</Grid>
</GroupBox>
MyRectangleControl is a usercontrol of something like
<UserControl x:Class="Server.Host.MyRectangleControl"
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"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="30">
<Grid>
<Rectangle HorizontalAlignment="Center"
Height="25"
Margin="0,0,0,0"
Stroke="Black"
VerticalAlignment="Center"
Width="25"
Fill="red">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Test,UpdateSourceTrigger=PropertyChanged}"
Value="True">
<Setter Property="Fill"
Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</Grid>
In the code behind the groupbox, I have something like
namespace Server.Host
{
public partial class SingleAxisControls : INotifyPropertyChanged
{
public SingleAxisControls()
{
InitializeComponent();
MyRectangle.DataContext = this;
}
private bool _test;
public bool Test
{
get { return _test; }
set
{
_test = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Test"));
}
}
}
private void OnButton_Click(object sender, RoutedEventArgs e)
{
Test = true;
}
private void OffButton_Click(object sender, RoutedEventArgs e)
{
Test = false;
}
}
I am not sure what is wrong but it doesn't seem change the color of the rectangle when i change the value of test from false to true.
This is a problem of value precedence.
When you set a DependencyProperty directly in the declaration of an Element this value has higher precedence than a value set in a style.
All you have to do is set the Fill property to Red in the Style:
<Rectangle HorizontalAlignment="Center"
Height="25"
Margin="0,0,0,0"
Stroke="Black"
VerticalAlignment="Center"
Width="25"
>
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Test,UpdateSourceTrigger=PropertyChanged}"
Value="True">
<Setter Property="Fill"
Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
Another valid option is writing a converter to the binding. It would look like this:
class BooleanToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, System.Globalization.CultureInfo culture)
{
if ((bool)value)
{
return new SolidColorBrush(Colors.Black);
}
return new SolidColorBrush(Colors.LightGray);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
(You can set the colors to anything you want)
and the Xaml would look like this:
<Rectangle HorizontalAlignment="Center"
Height="25"
Margin="0,0,0,0"
Stroke="Black"
VerticalAlignment="Center"
Width="25"
Fill="{Binding Test, Converter={StaticResource b2b}}">
with this at the top of your xaml:
<UserControl.Resources>
<BooleanToBrushConverter x:Key="b2b" />
</UserControl.Resources>
Note: if your converter is in a different location you will need to include it in the namespace and preface the declaration with whatever you named the namespace i.e.
xlmns:converters="clr-namespace:Project.Converters"
in the <UserControl> tag
and then <converters: BooleanToBrushConverter x:Key="b2b" /> in the resources

Custom WPF context menu with text on top

I created a custom context menu template which looks like this:
<Style TargetType="{x:Type ContextMenu}">
<Setter Property="Background" Value="{StaticResource menuBorderBrush}"/>
<Setter Property="Foreground" Value="{StaticResource menuForegroundBrush}"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContextMenu}">
<Border x:Name="Border" Background="{StaticResource menuBackgroundBrush}" BorderThickness="5" BorderBrush="{StaticResource menuBackgroundBrush}">
<StackPanel>
<TextBlock TextAlignment="Center" Padding="5,5,5,10">
You are not logged in.
</TextBlock>
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now i'd like to be able to change the "You are not logged in." text programmatically. I tried to start by creating a custom control class which inherits from ContextMenu and change the x:Type in the XAML to this class but then the Properties (like Background, Foreground, ...) are unknown.
How can i do this without implementing a new ContextMenu from scratch?
You can add to the TextBlock Binding with TemplatedParent for this.
<TextBlock TextAlignment="Center" Padding="5,5,5,10"
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Tag}" />
And add the text to the ContextMenu Tag property, like that.
<Grid.ContextMenu>
<ContextMenu Tag="Test menuItem text" />
</Grid.ContextMenu>
Update
There are some tricks to pass many properties. For example you can bind ContextMenu to viewModel with many properties. The view Model should have realization INotifyPropertyChanged behaviour. So you can write something like this, but with many properties.
public class OwnObject : INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged( "Text" ); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void NotifyPropertyChanged( String info )
{
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( info ) );
}
}
}
And pass this viewModel object through the Tag property.
<Grid.ContextMenu>
<ContextMenu Tag="{Binding Object}" />
</Grid.ContextMenu>
And use it in the menu Style through the Tag.Text.
<TextBlock TextAlignment="Center" Padding="5,5,5,10"
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Tag.Text}" />

XAML binding value

I am developing windows phone 8.1 app and I need circular progressbar.
I have this usercontrol code:
<UserControl.Resources>
<Style TargetType="ProgressBar" x:Key="CircularProgressBarStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Grid x:Name="LayoutRoot">
<local:CircularProgressBarViewModel.Attach>
<local:CircularProgressBarViewModel HoleSizeFactor="0.75"/>
</local:CircularProgressBarViewModel.Attach>
<Ellipse Width="{Binding Diameter}" Height="{Binding Diameter}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Stroke="LightGray" Opacity="0.5" Fill="Transparent"
StrokeThickness="10">
</Ellipse>
<local:PiePiece CentreX="{Binding CentreX}" CentreY="{Binding CentreY}"
RotationAngle="0" WedgeAngle="{Binding Angle}"
Radius="{Binding Radius}" InnerRadius="{Binding InnerRadius}"
Fill="Black" Opacity="0.7"/>
<Grid util:GridUtils.RowDefinitions="*,2*,*"
util:GridUtils.ColumnDefinitions="*,2*,*">
<Viewbox Grid.Row="1" Grid.Column="1">
<TextBlock Name="myValue" Text="{myValue value}"
Foreground="WhiteSmoke"
FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Viewbox>
</Grid></Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
How can I change value with the name "myValue" from code behind (for example, from MainPage.xaml.cs) and not from CircularProgressBarViewModel?
If you need to get myValue from your MainPage, where you are using your UserControl you can create DependencyProperty in your control and set any value from page to control.
public sealed partial class YourUserControl: UserControl
{
public static readonly DependencyProperty myValueProperty = DependencyProperty.Register("myValue", typeof(string), typeof(YourUserControl), new PropertyMetadata(string.Empty));
public YourUserControl()
{
this.InitializeComponent();
}
public string myValue
{
get { return (string)GetValue(myValueProperty ); }
set { SetValue(myValueProperty , value); }
}
}
And on the your MainPage like this:
<controls:YourUserControl myValue={Binding YourValue}/>

Categories