I have a custom ContentControl as follows.
Modal.xaml:
<Style TargetType="local:Modal">
<Setter Property="Background" Value="#65000000" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Modal">
<Grid Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
Visibility="{Binding IsOpen, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BoolToVisibilityConverter}}">
<Border x:Name="ContentBorder"
Padding="80">
<ContentControl HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<Grid x:Name="ContentGrid"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
CornerRadius="10">
<ContentPresenter Content="{TemplateBinding Content}"/>
</Grid>
</ContentControl>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How can I access the ContentBorder or ContentGrid controls inside of the code behind. I am saying if (control.GetTemplateChild("ContentBorder") is Border border) and it can't find it. Please help.
Code Behind is as follows:
Modal.cs:
public class Modal : ContentControl
{
public Modal()
{
DefaultStyleKey = typeof(Modal);
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register(nameof(IsOpen), typeof(bool), typeof(Modal), new PropertyMetadata(false));
public bool IsOpen
{
get => (bool)GetValue(IsOpenProperty);
set => SetValue(IsOpenProperty, value);
}
public static readonly DependencyProperty DialogMaxWidthProperty =
DependencyProperty.Register(nameof(DialogMaxWidth), typeof(double), typeof(Modal), new PropertyMetadata(0, OnMaxWidthSet));
private static void OnMaxWidthSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (Modal)d;
//GetTemplateChild is unable to find ContentBorder.
if (control.GetTemplateChild("ContentBorder") is Border border)
{
border.MaxWidth = (double)e.NewValue;
}
}
public double DialogMaxWidth
{
get => (double)GetValue(DialogMaxWidthProperty);
set => SetValue(DialogMaxWidthProperty, value);
}
}
I am using the custom control from somwhere outside as follows.
<controls:Modal x:Name="ContentModal"
Canvas.ZIndex="100"
Grid.Row="0"
Grid.RowSpan="2"
IsOpen="{x:Bind ViewModel.IsModalOpen, Mode=OneWay}"
DialogMaxWidth="450"/>
if DialogMaxWidth property change execute before OnApplyTemplate, GetTemplateChild will return null. so you must add a field and set it on OnApplyTemplate function. then check it on propertychaged
private Border brd;
public override void OnApplyTemplate()
{
brd = this.GetTemplateChild("ContentBorder") as Border;
if (brd!=null && DialogMaxWidth!=0)
brd.MaxWidth = DialogMaxWidth;
}
private static void OnMaxWidthSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (Modal)d;
//GetTemplateChild is unable to find ContentBorder.
if (control.brd != null && control.brd is Border)
{
control.brd.MaxWidth = (double)e.NewValue;
}
}
Related
I have an ItemsControl that is binding to an ObservableCollection "MenuButtons".
In the ItemsControl, I want to add some Buttons programmatically with Dependency Properties.
My problem is that the values I pass are not updated. The default values are displayed in the view.
C#
private void btnTest_Click(object sender, RoutedEventArgs e)
{
var vm = DataContext as UflMainWindowViewModel;
vm.MenuButtons.Add(new UflMenuButton { IconText="Test123", Style = (Style)Application.Current.Resources["UflMenuButtonStyle"] });
}
C# UflButtonClass
public class UflMenuButton : Button
{
public string IconText
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("IconText", typeof(string), typeof(UflMenuButton), new UIPropertyMetadata("default", new PropertyChangedCallback(IconTextChanged)));
private static void IconTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UflMenuButton button = (UflMenuButton)sender;
button.IconText = (string)e.NewValue;
}
}
with the following Style:
WPF
<Style x:Key="UflMenuButtonStyle" TargetType="{x:Type local:UflMenuButton}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=(local:UflMenuButton.IconText),RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
You can bind to the first ancestor of type UflMenuButton :
<TextBlock Text="{Binding IconText, RelativeSource={RelativeSource AncestorType={x:Type local:UflMenuButton}}}" HorizontalAlignment="Center"/>
This work, but I assume a more elegant way exists.
Hi I'm building a control I need to access checkbox in the code behind But I get the null error
this is my control
<Style TargetType="TreeViewItem" x:Key="CheckTreeViewItem" >
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}"/>
</Style>
<Style TargetType="local:CheckTreeView">
<Setter Property="ItemContainerStyle" Value="{StaticResource CheckTreeViewItem}"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<HierarchicalDataTemplate DataType="{x:Type local:CheckTreeSource}" ItemsSource="{Binding Children}">
<CheckBox x:Name="PART_CheckBox" Margin="1" IsChecked="{Binding IsChecked}">
<TextBlock Text="{Binding Text}"/>
</CheckBox>
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>
And this is how I access control
[TemplatePart(Name = CheckBox_Key, Type = typeof(CheckBox))]
public partial class CheckTreeView : TreeView
{
private const string CheckBox_Key = "PART_CheckBox";
CheckBox checkBox;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
checkBox = GetTemplateChild(CheckBox_Key) as CheckBox;
checkBox.Click += CheckBox_Click;
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
}
}
When I use the following code I get no null error but no control in runtime
static CheckTreeView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckTreeView),
new FrameworkPropertyMetadata(typeof(CheckTreeView)));
}
To subscribe to Click event of each CheckBox:
public partial class CheckTreeView : TreeView
{
public CheckTreeView()
{
InitializeComponent();
processNode(this);
}
void processNode(ItemsControl node)
{
node.ItemContainerGenerator.StatusChanged += (sender, args) =>
{
ItemContainerGenerator itemContainerGenerator = ((ItemContainerGenerator)sender);
if (itemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
for (int i = 0; i < itemContainerGenerator.Items.Count; i++)
{
TreeViewItem treeViewItem =
(TreeViewItem) itemContainerGenerator.ContainerFromIndex(i);
treeViewItem.Loaded += (o, eventArgs) =>
{
CheckBox checkBox = FindVisualChild<CheckBox>(treeViewItem);
checkBox.Click += CheckBox_Click;
};
processNode(treeViewItem);
}
}
};
}
public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
if (obj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
{
return (T)child;
}
T childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
}
return null;
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
}
}
Note that if you use ObservableCollection as items source, you should process changes in that collection: subscribe to events of added items, unsubscribe from events of removed items.
CheckBox_Key element is not a part of CheckTreeView.ControlTemplate. It will be a part in tree view nodes, multiple times. GetTemplateChild won't find it
I would say that intead of CheckBox in ItemTemplate for CheckTreeView, you need to change TreeViewItem.Template and add CheckBox there. It won't help with GetTemplateChild, but is is more logical approach to build reusable TreeView with chechboxes.
Here is an example. Check/Uncheck operations can be handled by specail Command in CheckTreeView class:
public class CheckTreeView: TreeView
{
public CheckTreeView()
{
CheckCommand = new RelayCommand<object>(o => MessageBox.Show(o?.ToString()));
}
public ICommand CheckCommand
{
get { return (ICommand)GetValue(CheckCommandProperty); }
set { SetValue(CheckCommandProperty, value); }
}
public static readonly DependencyProperty CheckCommandProperty =
DependencyProperty.Register("CheckCommand", typeof(ICommand), typeof(CheckTreeView), new PropertyMetadata(null));
}
relevant part of TreeViewItem template (use Edit template feature in WPF designer to get full template):
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<StackPanel Orientation="Horizontal">
<CheckBox Command="{Binding CheckCommand, RelativeSource={RelativeSource AncestorType={x:Type local:CheckTreeView}}}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="0,0,4,0"/>
<ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</StackPanel>
</Border>
<ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
</Grid>
</ControlTemplate/>
</Setter.Value>
</Setter>
</Style>
I have the following custom control based on the "heavy option" at this link:
public partial class SelectableContentControl : ContentControl
{
public SelectableContentControl()
{
InitializeComponent();
var isCheckedDesc = DependencyPropertyDescriptor.FromProperty(IsCheckedProperty, typeof(SelectableContentControl));
isCheckedDesc.AddValueChanged(this, IsCheckedPropertyChanged);
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool),
typeof(SelectableContentControl), new PropertyMetadata(false));
private void IsCheckedPropertyChanged(object sender, EventArgs e)
{
var selectable = Content as IAmSelectable;
if (selectable != null) selectable.IsSelected = IsChecked;
}
}
The style defined for the SelectableContentControl is as follows:
<Style TargetType="{x:Type controls1:SelectableContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls1:SelectableContentControl}">
<CheckBox IsChecked="{TemplateBinding IsChecked}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...and my usage:
<controls:SelectableContentControl Grid.Row="2" Content="{Binding Dummy}" IsChecked="{Binding Dummy.IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I want IsCheckedPropertyChanged to be called whenever the IsChecked value changes on the UI, but this isn't happening. Anyone see what I'm missing?
TemplateBinding works in a OneWay mode, meaning that the value is updated only in source-to-target direction (your control being the source, and the CheckBox inside the template the target). If you want the binding to work in TwoWay mode, you should use a normal Binding instead:
<ControlTemplate TargetType="{x:Type controls1:SelectableContentControl}">
<CheckBox IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
Note that you don't need to specify Mode=TwoWay on the binding, because CheckBox.IsChecked property binds in two-way mode by default.
See this question for more detailed info.
I came across something that maybe a bug in wpf listbox. Please see the code and then I explain what happens
Window
<Window >
<Grid>
<local:MultiSelectionComboBox Width="200"
MinHeight="25"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ASLDisplayMemberPath="FirstName"
ASLSelectedItems="{Binding SelectedModels}"
ItemsSource="{Binding Models}" />
<ListBox HorizontalAlignment="Right"
VerticalAlignment="Top"
DisplayMemberPath="FirstName"
ItemsSource="{Binding SelectedModels}" />
</Grid>
</Window>
User control
<ComboBox>
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid x:Name="MainGrid"
Width="Auto"
Height="Auto"
SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" />
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup"
Grid.ColumnSpan="2"
Margin="1"
AllowsTransparency="true"
IsOpen="{Binding Path=IsDropDownOpen,
RelativeSource={RelativeSource TemplatedParent}}"
Placement="Center"
PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}">
<Border x:Name="DropDownBorder"
MinWidth="{Binding Path=ActualWidth,
ElementName=MainGrid}"
MaxHeight="{TemplateBinding MaxDropDownHeight}">
<ScrollViewer CanContentScroll="true">
<StackPanel>
<CheckBox x:Name="checkBox"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}, Path=CheckAllCommand}">select all</CheckBox>
<ListBox x:Name="lstBox"
here lies the problem without the line below I dont get to see the result on start up,
with the it selects the first item even if nothing is triggering it
IsSynchronizedWithCurrentItem="True"
ItemsSource="{TemplateBinding ItemsSource}"
KeyboardNavigation.DirectionalNavigation="Contained"
SelectionChanged="ListBoxOnSelectionChanged"
SelectionMode="Multiple"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</StackPanel>
</ScrollViewer>
</Border>
</Popup>
<ToggleButton Grid.ColumnSpan="2"
MinWidth="20"
HorizontalAlignment="Right"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
IsChecked="{Binding Path=IsDropDownOpen,
Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Style="{DynamicResource ToggleButtonStyle1}" />
<ItemsControl x:Name="itemsControl"
Margin="4,2,0,2"
ItemsSource="{Binding Path=SelectedItems,
ElementName=lstBox}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="#383838"
CornerRadius="2"
Padding="5,2,3,2">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Test"
Foreground="White"
Loaded="Test_OnLoaded"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ComboBox}},
Path=ASLDisplayMemberPathActualValue}" />
<Button Width="12"
Height="12"
Margin="5,0,0,0"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ComboBox},
Path=UnselectCommand}"
CommandParameter="{Binding}">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse x:Name="PART_ButtonBackground"
Fill="White"
Opacity="0" />
<TextBlock x:Name="PART_Text"
Margin="0,0,0,1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="10"
Foreground="White"
Text="X" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="PART_ButtonBackground" Property="Opacity" Value="0.8" />
<Setter TargetName="PART_Text" Property="Foreground" Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
</ComboBox>
code behind user control
public partial class MultiSelectionComboBox : ComboBox
{
#region fields, dependencies, command and constructor
private ListBox listBox;
private ItemsControl itemsControl;
private CheckBox checkBox;
public static readonly DependencyProperty ASLSelectedItemsProperty = DependencyProperty.Register("ASLSelectedItems", typeof(ObservableCollection<object>), typeof(MultiSelectionComboBox), new PropertyMetadata(null));
public static readonly DependencyProperty ASLDisplayMemberPathProperty = DependencyProperty.Register("ASLDisplayMemberPath", typeof(string), typeof(MultiSelectionComboBox), new PropertyMetadata("Description"));
public MultiSelectionComboBox()
{
InitializeComponent();
}
public DelegateCommand<object> UnselectCommand { get; private set; }
public DelegateCommand CheckAllCommand { get; private set; }
public ObservableCollection<object> ASLSelectedItems
{
get { return (ObservableCollection<object>)GetValue(ASLSelectedItemsProperty); }
set { SetValue(ASLSelectedItemsProperty, value); }
}
public string ASLDisplayMemberPath
{
get { return (string)GetValue(ASLDisplayMemberPathProperty); }
set { SetValue(ASLDisplayMemberPathProperty, value); }
}
#endregion
private void CheckAll()
{
if (checkBox.IsChecked == true)
{
listBox.SelectAll();
}
else
{
listBox.UnselectAll();
}
}
private void GetControls()
{
checkBox = Template.FindName("checkBox", this) as CheckBox;
listBox = Template.FindName("lstBox", this) as ListBox;
itemsControl = Template.FindName("itemsControl", this) as ItemsControl;
}
private bool bug = true;
private void ListBoxOnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
e.AddedItems.Cast<object>().Where(item => !ASLSelectedItems.Contains(item)).ForEach(p => ASLSelectedItems.Add(p));
e.RemovedItems.Cast<object>().Where(item => ASLSelectedItems.Contains(item)).ForEach(p => ASLSelectedItems.Remove(p));
checkBox.IsChecked = (listBox.ItemsSource as IList).Count == listBox.SelectedItems.Count;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
GetControls();
SetControls();
UnselectCommand = new DelegateCommand<object>(p => listBox?.SelectedItems.Remove(p));
CheckAllCommand = new DelegateCommand(CheckAll);
}
private void SetControls()
{
listBox.DisplayMemberPath = ASLDisplayMemberPath;
itemsControl.DisplayMemberPath = ASLDisplayMemberPath;
}
private void Test_OnLoaded(object sender, RoutedEventArgs e)
{
(sender as TextBlock)?.SetBinding(TextBlock.TextProperty, ASLDisplayMemberPath);
}
}
view model
public class MainWindowViewModel
{
public ObservableCollection<Model> Models { get; set; }
public ObservableCollection<object> SelectedModels { get; set; }
public MainWindowViewModel()
{
Models = new ObservableCollection<Model>()
{
new Model() {FirstName = "Amelia", Age = 0},
new Model() {FirstName = "Bruno", Age = 5},
new Model() {FirstName = "Colin", Age = 47},
new Model() {FirstName = "Daniel", Age = 32},
new Model() {FirstName = "Iza", Age = 28},
new Model() {FirstName = "James", Age = 23},
new Model() {FirstName = "Simon", Age = 23}
};
SelectedModels = new ObservableCollection<object> {Models.FirstOrDefault() };
}
}
Now the problem is that if inside the user control (where the listbox is) if I don’t set synchronize to true, then it won’t see the first item on start-up, if I do set it, then something forces an add to the collection. And then even if inside selected children I don’t set a value, it still sets the value of the first child.
This is actually multiselect combobox, got so far and this one simple thing stopped me for half of the day. And I can't find what is causing it.
Any help would be appreciated
This will help us on how to understand how listbox behaves when IsSynchronizedWithCurrentItem is set to true. Link
Given your requirement, this should be false.
Your LisBox.SelectionChanged event handler will be invoked every time you set the Values to ASLSelectedItems.
I think it will work by:
Remove your handler from your ListBox (lstBox).
Then modify your DP
public static readonly DependencyProperty ASLSelectedItemsProperty = DependencyProperty.Register("ASLSelectedItems", typeof(ObservableCollection<object>), typeof(MultiSelectionComboBox), new PropertyMetadata(null, OnSelectedItemsChanged));
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Add your logic from your handler
}
The UserControl below works well but I would like to make it easier to change the Style.
One thing I have tried is to convert this to a Custom Control, but I am stuck on basics like how to set the ToolTip inside the static method that deals with a change in a property (see below)
The other thing I tried to move the Style into a generic button style in the ResourceDictionary, but that is the subject of this question
How can I set the ToolTip in my subclassed Button?
Cheers
UserControl XAML:
<UserControl.Resources>
<ResourceDictionary Source="pack://application:,,,/Smack.Core.Presentation.Wpf;component/Themes/generic.xaml" />
</UserControl.Resources>
<Button x:Name="_button" Style="{StaticResource blueButtonStyle}" Command="{Binding AddNewItemCommand}" >
<StackPanel Orientation="Horizontal" >
<Image Source="{resx:Resx ResxName=Smack.Core.Presentation.Resources.MasterDetail, Key=bullet_add}" Stretch="Uniform" VerticalAlignment="Center" />
<AccessText x:Name="_accesText" VerticalAlignment="Center">_Add New Subject</AccessText>
<ContentPresenter/>
</StackPanel>
</Button>
UserControl Code Behind:
public partial class AddNewItemButton : UserControl
{
public AddNewItemButton() { InitializeComponent(); }
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"Subject", typeof (string), typeof (AddNewItemButton),
new FrameworkPropertyMetadata(OnSubjectChanged));
public string Subject { get { return (string) GetValue(SubjectProperty); } set { SetValue(SubjectProperty, value); } }
private static void OnSubjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
var control = obj as AddNewItemButton;
if (control == null) return;
control._accesText.Text = "_" + string.Format(MasterDetail.Subject_AddNew_Label, control.Subject.Capitalize());
control._button.ToolTip = string.Format(MasterDetail.Subject_AddNew_ToolTip, control.Subject.ToLower());
}
}
Failed attempt to create a Custom Control:
public class MyButton : Button
{
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"ItemName", typeof(string), typeof(MyButton),
new FrameworkPropertyMetadata(OnSubjectChanged));
public string Subject
{
get { return (string)GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
private static void OnSubjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var control = obj as MyButton;
if (control == null) return;
ToolTip = ??;
}
}
UPDATE
Based on Phil's answer, the control (the bottom one) is more 'lookless' than I'd like :--)
Result
Code
public class AddNewItemButton : Button
{
static AddNewItemButton() {
var type = typeof (AddNewItemButton);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
}
#region Subject
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"Subject", typeof(string), typeof(AddNewItemButton),
new PropertyMetadata(default(string)));
public string Subject
{
get { return (string)GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
#endregion
}
Generic.xaml
<Style TargetType="{x:Type local:AddNewItemButton}">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=Subject, Converter={StaticResource AddNewItemForToolTip}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AddNewItemButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Stretch="Uniform" VerticalAlignment="Center"
Source="{resx:Resx ResxName=Smack.Core.Presentation.Resources.MasterDetail, Key=bullet_add}" />
<AccessText
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Subject, Converter={StaticResource AddNewItemForLabel}}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's an example of a custom button with a tooltip (based on the questions you've been asking recently):
This is the code
public class CustomButton : Button
{
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton),
new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public static readonly DependencyProperty SubjectProperty =
DependencyProperty.Register("Subject", typeof (string),
typeof (CustomButton), new PropertyMetadata(default(string)));
public string Subject
{
get { return (string) GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
}
This goes in Themes/generic.xaml
<System:String x:Key="Test">Add new: </System:String>
<Style TargetType="{x:Type local:CustomButton}">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=Subject}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Image here"
VerticalAlignment="Center" Padding="0,0,5,0"/>
<AccessText Grid.Column="1" VerticalAlignment="Center">
<AccessText.Text>
<MultiBinding StringFormat="{}_{0} {1}">
<Binding Source="{StaticResource Test}"/>
<Binding RelativeSource=
"{RelativeSource TemplatedParent}"
Path="Subject"/>
</MultiBinding>
</AccessText.Text>
</AccessText>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>