I have create a lookless control to be used in a Silverlight 4 project. This control contains a button and I would like to capture the click event. The Generic.xaml contains
<Style TargetType="TU:MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TU:MyControl" >
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" d:DesignWidth="550" d:DesignHeight="228">
<Grid Background="Silver">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150*"/>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="150*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Margin="2" BorderBrush="DarkGray" BorderThickness="3"></Border>
<Border Grid.Column="2" Margin="2" BorderBrush="DarkGray" BorderThickness="3"></Border>
<StackPanel Grid.Column="1">
<Button Name="PART_MyClick" Height="32" Width="32" Margin="0,8,0,0"></Button>
</StackPanel>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
After researching the problem I beleive that I have to add the following attribute to my control class
[TemplatePart(Name = "PART_MyClick", Type = typeof(Button))]
Then in my controls constructor I have added the following code
var myClick = GetTemplateChild("PART_MyClick") as Button;
if(myClick != null)
{
myClick.Click += (o, e) => DoThings();
}
when run though the myClick variable is always null so the event handler never gets attached. Could you please tell me where I am going wrong? Im a newbie so if this is the wrong approach completly then any advise on the correct approach would also be greatfully received
Override the OnApplyTemplate method and put your code there instead of the control's constructor:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var myClick = GetTemplateChild("PART_MyClick") as Button;
if(myClick != null)
{
myClick.Click += (o, e) => DoThings();
}
}
Because during the constructor call the visual tree for the control is not build up yet. From MSDN OnApplyTemplate:
Attach class-defined event handlers to parts of the template. For
example, you might want class logic to handle KeyDown events from a
TextBox template part so that UI states are updated, and other events
that are specific to your control are raised instead.
Related
I'm working on a custom WPF control. The main purpose of this control is to visualize thousands of graphical primitives in a scrollable area. The core part of the control's template looks like this:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ItemVisualizer}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<local:ItemAreaElement Grid.Row="0" Grid.Column="0" x:Name="PART_ItemArea" />
<ScrollBar Grid.Row="0" Grid.Column="1" x:Name="PART_ScrollBarVert" Orientation="Vertical" Maximum="100" />
<ScrollBar Grid.Row="1" Grid.Column="0" x:Name="PART_ScrollBarHorz" Orientation="Horizontal" Maximum="100" />
<Rectangle Grid.Row="1" Grid.Column="1" x:Name="PART_SizeGrip" Focusable="False" Fill="#F0F0F0" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
The ItemAreaElement is responsible for drawing the items. For simplicity, we can think that its core part looks like this:
class ItemAreaElement : FrameworkElement
{
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
for (int i = 0; i < _data.ItemCount; i++)
{
drawingContext.DrawLine(_penLine, new Point(0, i * 10), new Point(100, i * 10));
}
}
}
I need to repaint the ItemAreaElement every time when a related property in the whole ItemVisualizer control changes. However, I didn't find a way to do that in WPF. The well know trick with the Dispatcher object does not work in my case:
private static void OnItemCountPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
ItemVisualizer vis = (ItemVisualizer)source;
vis._itemArea.Dispatcher.Invoke(delegate { }, DispatcherPriority.Render);
}
, where _itemArea is a local reference to the ItemAreaElement got in OnApplyTemplate():
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (this.Template != null)
{
_itemArea = this.Template.FindName("PART_ItemArea", this) as ItemAreaElement;
_itemArea.Grid = this;
}
}
Are there other ways to force an update of the UIElement in my construction? Or maybe, I need to redesign the whole control to make it possible?
It should usually be sufficient to specify FrameworkPropertyMetadataOptions.AffectsRender on registration of the ItemCount property:
public static readonly DependencyProperty ItemCountProperty =
DependencyProperty.Register(
"ItemCount", typeof(int), typeof(ItemVisualizer),
new FrameworkPropertyMetadata(FrameworkPropertyMetadataOptions.AffectsRender));
If that doesn't help, you could force a redraw by calling InvalidVisual() on the ItemAreaElement:
var vis = (ItemVisualizer)source;
vis._itemArea.InvalidVisual();
I posted a question in this link. maybe I'm not well expressed.
It's very simple, I want to change a property in a usercontrol or CustomControl after a click on a Boutton outside...
The code of the customcontrol is as follows :
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border x:Name="container" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Hidden" Value="true">
<Setter Property="BorderBrush" Value="Blue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public class CustomControl1 : Control
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public bool Hidden
{
get { return (bool)GetValue(HiddenProperty); }
set { SetValue(HiddenProperty, value); }
}
// Using a DependencyProperty as the backing store for Hidder. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HiddenProperty =
DependencyProperty.Register("Hidden", typeof(bool), typeof(CustomControl1), new PropertyMetadata(false));
}
And a simple window for test
<Window x:Class="WpfTestCustomControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomBorder;assembly=WpfCustomBorder"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<local:CustomControl1 x:Name="cc" BorderBrush="Red" BorderThickness="3" Margin="10" Grid.RowSpan="2"/>
<Button Grid.Column="1" Content="Ok" Margin="5" Click="Button_Click"/>
</Grid>
namespace WpfTestCustomControl
{
/// <summary>
/// Logique d'interaction pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
cc.Hidden = true;
}
}
}
The property "Hidden" is a dependency property inside the custom control.
When i click on the button in mainwindow i want to change the hidden property to true. this must fire the trigger inside the custom control to change borderbrush to "blue" color. While nothing happen.
Is there something missing or is not the right way to do it ?
Thanks in advance..
Don't hard-set BorderBrush="Red" in your Control's declaration, it's prioritary over any trigger's setter.
You might want to check msdn's Dependency Property Value Precedence
I have been working with WPF for some time.
I need to create the following control over Internet, but could not find appropriate.
Can anybody help how to implement this functionality. Value should be increasing or decreasing when clicked on control.
I found that I can use either Volume control or Slider, but not getting clear what I should use.
Thanks in anticipation.
I prefer to use a Progressbar for these kind of displays.
This is my implementation of a simple volume control looking pretty much like the one you show as an example:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private double _volume;
private bool mouseCaptured = false;
public double Volume
{
get { return _volume; }
set
{
_volume = value;
OnPropertyChanged("Volume");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void MouseMove(object sender, MouseEventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed && mouseCaptured)
{
var x = e.GetPosition(volumeBar).X;
var ratio = x/volumeBar.ActualWidth;
Volume = ratio*volumeBar.Maximum;
}
}
private void MouseDown(object sender, MouseButtonEventArgs e)
{
mouseCaptured = true;
var x = e.GetPosition(volumeBar).X;
var ratio = x / volumeBar.ActualWidth;
Volume = ratio * volumeBar.Maximum;
}
private void MouseUp(object sender, MouseButtonEventArgs e)
{
mouseCaptured = false;
}
#region Property Changed
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And the XAML:
<Window x:Class="VolumeControlApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="196" Width="319">
<Window.Resources>
<Style x:Key="VolumeStyle" TargetType="{x:Type ProgressBar}">
<Setter Property="Foreground" Value="#FFB00606"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid x:Name="TemplateRoot">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"/>
<Rectangle x:Name="PART_Track"/>
<Grid x:Name="PART_Indicator" ClipToBounds="True" HorizontalAlignment="Left">
<Rectangle x:Name="Indicator" Fill="{TemplateBinding Foreground}" RadiusX="5" RadiusY="3"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Background="#FF363636">
<Border Background="Gray" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" CornerRadius="3" Padding="2">
<Border Background="Black" CornerRadius="3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Vol:" Foreground="White" VerticalAlignment="Center" Margin="4 0"/>
<ProgressBar x:Name="volumeBar" Height="10"
Value="{Binding Volume}"
Width="100"
MouseMove="MouseMove"
MouseDown="MouseDown"
MouseUp="MouseUp" Style="{DynamicResource VolumeStyle}" Grid.Column="1"/>
</Grid>
</Border>
</Border>
</Grid>
</Window>
You could use a slider and create a template for it.
If you need special mouse handling you'll need to subclass the slider and add logic/event handling.
The standard Slider template has a couple of repeat buttons. By simply making the left repeat button red you have a very basic implementation of the required control.
Take a look at this posts hope it helps you..
Link:
1: Sliders
2: Similar to VLC player volume control
I created a silverlight template control. Thouse control consist 4 elements: 2 textbox and 2 textblock.
markup (in generic.xaml):
<Style TargetType="local:InputForm">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:InputForm">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Login" Grid.Column="0" Grid.Row="0"/>
<TextBlock Text="Password" Grid.Column="0" Grid.Row="1"/>
<TextBox x:Name="LoginTextBox" Grid.Column="1" Grid.Row="0" Text="Login..."/>
<TextBox x:Name="PasswordTextBox" Grid.Column="1" Grid.Row="1" Text="Password..."/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In code file I get the textbox from template and set Foreground.Opacity property equels 0.5.
code:
public class InputForm : Control
{
private TextBox _loginTextBox;
private TextBox _passwordTextBox;
public InputForm()
{
this.DefaultStyleKey = typeof(InputForm);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_loginTextBox = this.GetTemplateChild("LoginTextBox") as TextBox;
_passwordTextBox = this.GetTemplateChild("PasswordTextBox") as TextBox;
SetInActive();
}
private void SetInActive()
{
_loginTextBox.Foreground.Opacity = .5;
_passwordTextBox.Foreground.Opacity = .5;
}
}
When I added this control in my silverlight application all textboxs element began represent text with Foreground.Opacity = 0.5
Start application:
Select "Login" tab:
Back to "Some infromation" tab:
Sample located here: http://perpetuumsoft.com/Support/silverlight/SilverlightApplicationOpacity.zip
Is it silverlight bug or I do something wrong?
The problem is that the Foreground property is of type Brush which is a reference type (a class).
When you assign .Opacity = 0.5 you are changing the opacity value of the referenced Brush. All other elements that are referencing the same brush will be affected.
Ordinarily we would use a Storyboard in VisualStateManager in the control template to specify the visual appearance of a control in different "states".
However a quick fix for your code would be:
private void SetInActive()
{
Brush brush = new SolidColorBrush(Colors.Black) { Opacity = 0.5 };
_loginTextBox.Foreground = brush
_passwordTextBox.Foreground= brush
}
Since Popup doesn't derive from Control and doesn't have a template, how can I define a template so that all popups look the same? I need to design one that has a certain look and don't want to have to copy markup each time one is used.
This seems pretty easy but I can't figure out how to do it. The Child property defines a logical tree but I don't see how you can pull that out into a template and reuse it.
I was looking to do the same thing and here is what I came up with:
I inherited from ContentPresenter, styled that control as I wanted and than placed the derived ContentPresenter inside my Popup, I only used 2 text blocks for the simplicity but it is easy to understand how any content could be added.
My custom control:
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
[TemplatePart(Name = PART_PopupHeader, Type = typeof(TextBlock))]
[TemplatePart(Name = PART_PopupContent, Type = typeof(TextBlock))]
public class CustomPopupControl : ContentControl
{
private const string PART_PopupHeader = "PART_PopupHeader";
private const string PART_PopupContent = "PART_PopupContent";
private TextBlock _headerBlock = null;
private TextBlock _contentBlock = null;
static CustomPopupControl()
{
DefaultStyleKeyProperty.OverrideMetadata
(typeof(CustomPopupControl),
new FrameworkPropertyMetadata(typeof(CustomPopupControl)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_headerBlock = GetTemplateChild(PART_PopupHeader) as TextBlock;
_contentBlock = GetTemplateChild(PART_PopupContent) as TextBlock;
}
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register("HeaderText", typeof(string), typeof(CustomPopupControl), new UIPropertyMetadata(string.Empty));
public string HeaderText
{
get
{
return (string)GetValue(HeaderTextProperty);
}
set
{
SetValue(HeaderTextProperty, value);
}
}
public static readonly DependencyProperty ContentTextProperty =
DependencyProperty.Register("ContentText", typeof(string), typeof(CustomPopupControl), new UIPropertyMetadata(string.Empty));
public string ContentText
{
get
{
return (string)GetValue(ContentTextProperty);
}
set
{
SetValue(ContentTextProperty, value);
}
}
}
}
Style for the control:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControls">
<Style TargetType="{x:Type local:CustomPopupControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomPopupControl}">
<Border CornerRadius="3" BorderThickness="1" BorderBrush="White">
<Border.Background>
<SolidColorBrush Color="#4b4b4b" Opacity="0.75"/>
</Border.Background>
<Border.Effect>
<DropShadowEffect ShadowDepth="0"
Color="White"
Opacity="1"
BlurRadius="5"/>
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{TemplateBinding HeaderText}"
Grid.Row="0"
Foreground="#5095d6"
FontWeight="Bold"
VerticalAlignment="Bottom"
Margin="{TemplateBinding Margin}"
HorizontalAlignment="Left"/>
<Rectangle Grid.Row="1" Stroke="AntiqueWhite" Margin="1 0"></Rectangle>
<TextBlock Grid.Row="2"
Grid.ColumnSpan="2"
x:Name="PART_TooltipContents"
Margin="5, 2"
Text="{TemplateBinding ContentText}"
TextWrapping="Wrap"
MaxWidth="200"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The use of the control:
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="Button1" Content="Button with popup" HorizontalAlignment="Center">
</Button>
<Button x:Name="Button2" Content="Another button with popup" HorizontalAlignment="Center">
</Button>
<Popup IsOpen="True"
FlowDirection="LeftToRight"
Margin="10"
PlacementTarget="{Binding ElementName=Button1}"
Placement="top"
StaysOpen="True">
<local2:CustomPopupControl HeaderText="Some Header Text" ContentText="Content Text that could be any text needed from a binding or other source" Margin="2">
</local2:CustomPopupControl>
</Popup>
<Popup IsOpen="True"
FlowDirection="LeftToRight"
Margin="10"
PlacementTarget="{Binding ElementName=Button2}"
Placement="Bottom"
StaysOpen="True">
<local2:CustomPopupControl HeaderText="Different header text" ContentText="Some other text" Margin="2">
</local2:CustomPopupControl>
</Popup>
</StackPanel>
I tried illustrating how some properties can be constant across all controls, others can be customized per control and others could be bound to TemplatePart, here is the final result:
Depends how you want your pop-ups to behave. If they're just for displaying information in a uniform manner, than you might want to have a class that derives from Window that has the standard formats and styling wrapped around a ContentPresenter then bind the content of the presenter to a property which can represent the custom information for each pop-up.
Then its just a matter of programatically inserting whatever custom content you want before displaying the pop-up window.
Hope it helps.