I have a Button containing a Hyperlink, like so:
<Button IsEnabled="False">
<Hyperlink IsEnabled="True">Testing</Hyperlink>
</Button>
I need the Hyperlink to be enabled, however the Button to be disabled. How can I achieve this?
The above simply results in both controls being disabled.
I solved this problem by creating a simple wrapper element that breaks the IsEnabled inheritance chain from the parent.
The framework's default coerce callback checks the parent IsEnabled value and inherits it. This control sets a new coerce callback that just returns the value directly without checking inheritance.
public class ResetIsEnabled : ContentControl
{
static ResetIsEnabled()
{
IsEnabledProperty.OverrideMetadata(
typeof(ResetIsEnabled),
new UIPropertyMetadata(
defaultValue: true,
propertyChangedCallback: (_, __) => { },
coerceValueCallback: (_, x) => x));
}
}
In the example from the question it would be used like this:
<Button IsEnabled="False">
<ResetIsEnabled>
<!-- Child elements within ResetIsEnabled have IsEnabled set to true (the default value) -->
<Hyperlink>Testing</Hyperlink>
</ResetIsEnabled>
</Button>
Control Hyperlink has strangely with the property IsEnabled. In addition to the one that you mentioned, namely, the full value inheritance from a parent, there is another similar.
Hyperlink for the specific control, which has been turned off (IsEnabled="False"), setting (IsEnabled="True") will not update the Hyperlink property. The solution - use a relative source for Hyperlink (more info).
For solving your question, I have decided that it is not the standard way to solve. So I created a Class with its own dependencies properties. It has it's property MyIsEnabled and MyStyle. As you might guess from the title, the first sets its property IsEnabled and MyStyle need to specify the button style, simulating the IsEnabled="False" behavior.
SimulateDisable Style
<Style x:Key="SimulateDisable" TargetType="{x:Type Button}">
<Setter Property="Opacity" Value="0.5" />
<Setter Property="Background" Value="Gainsboro" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border CornerRadius="4" BorderThickness="1" BorderBrush="DarkBlue" SnapsToDevicePixels="True">
<ContentPresenter x:Name="MyContentPresenter" Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Define Button with yours properties:
<Button Name="MyButton" local:MyClass.MyIsEnabled="False" local:MyClass.MyStyle="{StaticResource SimulateDisable}" Width="100" Height="30" Click="Button_Click">
<Hyperlink IsEnabled="True" Click="Hyperlink_Click">Testing</Hyperlink>
</Button>
Listing of MyClass
public class MyClass : DependencyObject
{
public static readonly DependencyProperty MyIsEnabledProperty;
public static readonly DependencyProperty MyStyleProperty;
#region MyIsEnabled
public static void SetMyIsEnabled(DependencyObject DepObject, bool value)
{
DepObject.SetValue(MyIsEnabledProperty, value);
}
public static bool GetMyIsEnabled(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(MyIsEnabledProperty);
}
#endregion MyIsEnabled
#region MyStyle
public static void SetMyStyle(DependencyObject DepObject, Style value)
{
DepObject.SetValue(MyStyleProperty, value);
}
public static Style GetMyStyle(DependencyObject DepObject)
{
return (Style)DepObject.GetValue(MyStyleProperty);
}
#endregion MyStyle
static MyClass()
{
MyIsEnabledProperty = DependencyProperty.RegisterAttached("MyIsEnabled",
typeof(bool),
typeof(MyClass),
new UIPropertyMetadata(false, OnPropertyChanged));
MyStyleProperty = DependencyProperty.RegisterAttached("MyStyle",
typeof(Style),
typeof(MyClass),
new UIPropertyMetadata(OnPropertyChanged));
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Button MyButton = sender as Button;
bool MyBool = GetMyIsEnabled(MyButton);
if (MyBool == false)
{
MyButton.Style = MyClass.GetMyStyle(MyButton);
}
}
}
Plus for the event Hyperlink pointing e.Handled = true, so that the event did not happen next.
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hyperlink Click!");
e.Handled = true;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Button Click! Don't show it's!");
}
Output
P.S. Sorry for late answer :).
Related
I'm trying to create a custom dropdown control that acts like a ComboBox, such that the Popup opens when you click mouse down (not up), and closes when you click outside of the control.
The problem is that it only behaves if I set ClickMode to "Release". But what I really want is ClickMode="Press", such that the Popup opens on MouseDown instead of MouseUp.
But when I set it to ClickMode="Press", the popup won't close when you click outside the control.
Any ideas how I can achieve this?
Usage :
<StackPanel>
<local:CustomDropdown Width="200"
Height="50"
Content="Custom!" />
<ComboBox Width="200"
Margin="20">
<ComboBoxItem>A</ComboBoxItem>
<ComboBoxItem>B</ComboBoxItem>
<ComboBoxItem>C</ComboBoxItem>
</ComboBox>
</StackPanel>
Class :
internal class CustomDropdown : ContentControl
{
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(CustomDropdown), new PropertyMetadata(false));
}
Xaml :
<Style TargetType="{x:Type local:CustomDropdown}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<ToggleButton IsChecked="{Binding IsOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"/>
<ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<Popup StaysOpen="False"
Placement="Bottom"
IsOpen="{Binding IsOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<Border Background="White"
BorderBrush="Black"
BorderThickness="1"
Padding="50">
<TextBlock Text="Popup!" />
</Border>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
If you want it to work as expected with ClickMode.Press, you should programmatically set the IsOpen property to false whenever you want to the close the Popup. For example whenever you detect a click outside of the ToggleButton.
You could for example handle the PreviewMouseLeftButtonDown event for the parent window in your control. Something like this:
internal class CustomDropdown : ContentControl
{
private ToggleButton _toggleButton;
public CustomDropdown()
{
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_toggleButton = GetTemplateChild("toggleButton") as ToggleButton;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Window window = Window.GetWindow(this);
window.PreviewMouseLeftButtonDown += OnWindowPreviewMouseLeftButtonDown;
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Window window = Window.GetWindow(this);
window.PreviewMouseLeftButtonDown -= OnWindowPreviewMouseLeftButtonDown;
}
private void OnWindowPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ToggleButton toggleButton = FindParent<ToggleButton>(e.OriginalSource as DependencyObject);
if (toggleButton != _toggleButton)
IsOpen = false;
}
private static T FindParent<T>(DependencyObject dependencyObject) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null)
return null;
var parentT = parent as T;
return parentT ?? FindParent<T>(parent);
}
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(CustomDropdown), new PropertyMetadata(false));
}
}
XAML:
<ControlTemplate>
<Grid>
<ToggleButton x:Name="toggleButton" ...
You already have a working answer. However, finding the parent Window and parent ToggleButton can impact performance (depending on the depth of the visual tree).
As an alternative solution I suggest to focus on handling the Popup instead.
There are two conditions that prevent the Popup from closing itself: the button is configured with the ButtonBase.ClickMode set to ClickMode.Pressed AND the user is not clicking anything focusable inside the Popup.
If one of those two conditions evaluates to false (=> ClickMode.Release or the user has moved focus inside the Popup) your code will work as you would expected it to work.
Note that in order to allow the user to move focus inside the Popup, there must be a child that is focusable (UIElement.Focusable is set to true - it's false by default for most controls that don't require user interaction). For example, TextBlock is not focusable by default.
Because you want to keep the button configured to raise the Click event on mouse button press, you have to move the focus manually. But when you set it manually, the Popup won't receive a mouse click to setup itself to watch the focus. Therefore, you will end up closing the Popup manually (taking away the related control from the Popup).
The following example closes the Popup by observing the Mouse.PreviewMouseDownOutsideCapturedElement event to identify when the focus has moved away from the CustomDropdown control (mouse click outside the Popup):
CustomDropdown.cs
internal class CustomDropdown : ContentControl
{
public bool IsOpen
{
get => (bool)GetValue(IsOpenProperty);
set => SetValue(IsOpenProperty, value);
}
public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
"IsOpen",
typeof(bool),
typeof(CustomDropdown),
new PropertyMetadata(default(bool), OnIsOpenChanged));
public CustomDropdown()
{
Mouse.AddPreviewMouseDownOutsideCapturedElementHandler(this, OnPreviewMouseDownOutsideCapturedElement);
}
private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool isOpen = (bool)e.NewValue;
if (isOpen)
{
_ = Mouse.Capture(d as IInputElement, CaptureMode.SubTree);
}
else
{
_ = Mouse.Capture(null);
}
}
// Manually close the Popup if click is recorded outside the CustomDropdown/Popup
private void OnPreviewMouseDownOutsideCapturedElement(object sender, MouseButtonEventArgs e)
{
SetCurrentValue(IsOpenProperty, false);
}
}
I'm trying to get all the DataGrid Expanders to open and close using a open/close button like the example below. Problem is when I'm using a single boolean to bind to this will break when I'm opening/closing a single expander.
I'm using a ListCollectionView to set the GroupingDescription.
I guess I'm looking for a solution to somehow get the Expander to play nice?
View
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="{Binding Source={StaticResource proxy}, Path=Data.Expanded, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<Expander.Header>
<StackPanel>
<!-- label -->
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
ViewModel
public bool Expanded
{
get { return _expanded; }
set { _expanded = value; OnPropertyChanged(); }
}
public ListCollectionView Items
{
get
{
return _items;
}
set
{
_items = value; OnPropertyChanged();
}
}
// logic
var items = new ListCollectionView(planninglist);
items.SortDescriptions.Add(new items.GroupDescriptions.Add(new PropertyGroupDescription("AanZet"));
items.IsLiveSorting = true;
Update
Fix based on SO answer suggested in the comments by #XAMlMAX.
public class ExpanderBehavior
{
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.RegisterAttached("IsExpanded",
typeof(bool),
typeof(ExpanderBehavior),
new PropertyMetadata(OnChanged));
public static bool GetIsExpanded(DependencyObject obj)
{
return (bool)obj.GetValue(IsExpandedProperty);
}
public static void SetIsExpanded(DependencyObject obj, bool value)
{
obj.SetValue(IsExpandedProperty, value);
}
private static void OnChanged(DependencyObject o,
DependencyPropertyChangedEventArgs args)
{
Expander tb = o as Expander;
if (null != tb)
tb.IsExpanded = (bool)args.NewValue;
}
}
As per comment conversation.
Reason why you were experiencing issue with expander staying open even though it was bound is because when you use OneWay Binding and then click on the button it then becomes disconnected. To have a better idea on when it becomes disconnected use PresentationTraceSources.TraceLevel=High.
To overcome this, one can use an attached property to stop Binding from detaching.
Now this example is for ToggleButton but the logic should apply the same.
public class TBExtender
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked",
typeof(bool),
typeof(TBExtender),
new PropertyMetadata(OnChanged));
public static bool GetIsChecked(DependencyObject obj)
{
return (bool)obj.GetValue(IsCheckedProperty);
}
public static void SetIsChecked(DependencyObject obj, bool value)
{
obj.SetValue(IsCheckedProperty, value);
}
private static void OnChanged(DependencyObject o,
DependencyPropertyChangedEventArgs args)
{
ToggleButton tb = o as ToggleButton;
if (null != tb)
tb.IsChecked = (bool)args.NewValue;
}
}
Credit goes to alex-p
and wallstreet-programmer
for responses on this SO question.
Code behind:
public class LoginButton : Button
{
public static DependencyProperty LoginedProperty;
public static DependencyProperty LoginEventProperty;
public delegate void LoginEventDelegate(object sender, RoutedEventArgs e);
static LoginButton()
{
LoginedProperty = DependencyProperty.Register("Logined", typeof(Boolean), typeof(LoginButton));
LoginEventProperty = DependencyProperty.Register("LoginEvent", typeof(LoginEventDelegate), typeof(LoginButton));
}
public Boolean Logined
{
get { return (Boolean)base.GetValue(LoginedProperty); }
set { base.SetValue(LoginedProperty, value); }
}
public event LoginEventDelegate LoginEvent;
protected virtual void OnLoginEvent(object sender, RoutedEventArgs e)
{
if (LoginEvent != null)
LoginEvent(sender,e);
}
}
XAML:
<ControlTemplate x:Key="LoginButtonTemplate" TargetType="local:LoginButton">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ContentPresenter VerticalAlignment="Center" Margin="10,0,0,0"></ContentPresenter>
<Grid Grid.Column="1" Margin="5,0,0,0">
<Label Name="L" VerticalAlignment="Center" Padding="0" Foreground="#919191" Visibility="Collapsed">logined</Label>
<Button Name="B" Template="{StaticResource CustomButton}" Background="Transparent" BorderThickness="0" Padding="0" Foreground="#3598db" Content="click login"></Button>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Logined" Value="true">
<Setter TargetName="L" Property="Visibility" Value="Visible"></Setter>
<Setter TargetName="B" Property="Visibility" Value="Collapsed"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
--------------------------------
I want to achieve this function:
The button which Name="B" in ControlTemplate,when I click it,it work the custom event OnLoginEvent?
How can i do it?
--------------------------------
Oh,it seems no one knows my meaning for my poor English.
Now I explain the function for more detailed.
The software needs to manage about 100 or more account,if the account is not logined,it will show the button let user to login.And if is logined it will show the label that is logined
So I make a new Custom usercontrol named LoginButton,and create a boolean 'Logined' to control button if is logined.
Beaucase of different account has different login function.So I create a new event 'LoginEvent' to apply different login function.Now the question is the button Name="B" which to login.I need binding the button Name="B" click event or previewmousedown event to the event 'LoginEvent'.But I can't find the way to binding it.
Please help me,thanks a lot.
You could override the OnApplyTemplate method of the LoginButton class and hook up an event handler to the click event for the "B" button in the template that raises the event.
LoginEvent should be an event and not a dependency property though.
Try this:
public class LoginButton : Button
{
public static DependencyProperty LoginedProperty;
public delegate void LoginEventDelegate(object sender, RoutedEventArgs e);
static LoginButton()
{
LoginedProperty = DependencyProperty.Register("Logined", typeof(Boolean), typeof(LoginButton));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button b = this.Template.FindName("B", this) as Button;
if(b != null)
{
b.Click += (s, e) => OnLoginEvent(s, e);
}
}
public Boolean Logined
{
get { return (Boolean)base.GetValue(LoginedProperty); }
set { base.SetValue(LoginedProperty, value); }
}
public event LoginEventDelegate LoginEvent;
protected virtual void OnLoginEvent(object sender, RoutedEventArgs e)
{
if (LoginEvent != null)
LoginEvent(sender, e);
}
}
Usage:
<local:LoginButton Template="{StaticResource LoginButtonTemplate}" LoginEvent="LoginButton_LoginEvent"/>
private void LoginButton_LoginEvent(object sender, RoutedEventArgs e)
{
MessageBox.Show("Login!");
}
Bounty Rewarded for any solid tutorial/learning resources regarding wiring up events with templated controls.
I Have a control template like this:
<Style TargetType="local:DatePicker">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:DatePicker">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" x:Name="myDatePickerContentArea">
<StackPanel Orientation="Vertical">
<Button x:Name="myTestButton" Content="Test button" />
<telerik:RadDatePicker Style="{StaticResource VisitsReportTextBoxStyle}" Foreground="#FFFFFF" x:Name="startDate" DateTimeWatermarkContent="Start Date"/>
<telerik:RadDatePicker Style="{StaticResource VisitsReportTextBoxStyle}" x:Name="endDate" DateTimeWatermarkContent="End Date"/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The C# for this template is:
public class DatePicker : Control
{
public static readonly DependencyProperty StartDateSelectedDateProperty = DependencyProperty.Register("StartDateSelectedDateProperty", typeof(DateTime), typeof(DatePicker), null);
public DateTime? StartDateSelectedDate { get; set; }
public DatePicker()
{
this.DefaultStyleKey = typeof(DatePicker);
}
public override void OnApplyTemplate()
{
RadDatePicker StartDate = this.GetTemplateChild("startDate") as RadDatePicker;
StartDate.SelectionChanged += new Telerik.Windows.Controls.SelectionChangedEventHandler(StartDate_SelectionChanged);
StartDate.SelectedDate = new DateTime(2010, 01, 01);
base.OnApplyTemplate();
}
void StartDate_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangedEventArgs e)
{
RadDatePicker temp = (RadDatePicker)sender;
StartDateSelectedDate = temp.SelectedDate;
}
}
My selectionChanged Event Doesn't Fire and I'm not sure why.
Any Ideas ?
Here is an example of using best practice with the sort of control I think you are attempting to build (see notes at end for some explanations):-
[TemplatePart(Name = DatePicker.ElementStartDate, Type = typeof(RadDatePicker))]
[TemplatePart(Name = DatePicker.ElementEndDate, Type = typeof(RadDatePicker))]
public class DatePicker : Control
{
public DatePicker()
{
this.DefaultStyleKey = typeof(DatePicker);
}
#region Template Part Names
private const string ElementStartDate = "startDate";
private const string ElementEndDate = "endDate";
#endregion
#region Template Parts
private RadDatePicker _StartDate;
internal RadDatePicker StartDate
{
get { return _StartDate; }
private set
{
if (_StartDate != null)
{
_StartDate.SelectionChanged -= StartDate_SelectionChanged;
}
_StartDate = value;
if (_StartDate != null)
{
_StartDate.SelectionChanged += StartDate_SelectionChanged;
}
}
}
private RadDatePicker _EndDate;
internal RadDatePicker EndDate
{
get { return _EndDate; }
private set
{
if (_EndDate!= null)
{
_EndDate.SelectionChanged -= EndDate_SelectionChanged;
}
_EndDate= value;
if (_EndDate!= null)
{
_EndDate.SelectionChanged += EndDate_SelectionChanged;
}
}
}
#endregion
public static readonly DependencyProperty StartDateSelectedDateProperty =
DependencyProperty.Register(
"StartDateSelectedDateProperty",
typeof(DateTime?),
typeof(DatePicker),
new PropertyMetaData(new DateTime(2010, 01, 01)));
public DateTime? StartDateSelectedDate
{
get { return (DateTime?)GetValue(StartDateSelectedDateProperty); }
set { SetValue(StartDateSelectedDateProperty)}
}
public static readonly DependencyProperty EndDateSelectedDateProperty =
DependencyProperty.Register(
"EndDateSelectedDateProperty",
typeof(DateTime?),
typeof(DatePicker),
new PropertyMetaData(new DateTime(2010, 01, 01)));
public DateTime? EndDateSelectedDate
{
get { return (DateTime?)GetValue(EndDateSelectedDateProperty); }
set { SetValue(EndDateSelectedDateProperty)}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
StartDate = GetTemplateChild(ElementStartDate) as RadDatePicker;
EndDate = GetTemplateChild(ElementEndDate) as RadDatePicker;
}
void StartDate_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangedEventArgs e)
{
// Do stuff with StartDate here
}
void EndDate_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangedEventArgs e)
{
// Do stuff with EndDate here
}
}
The template Xaml should look like:-
<Style TargetType="local:DatePicker">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:DatePicker">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" x:Name="myDatePickerContentArea">
<StackPanel Orientation="Vertical">
<Button x:Name="myTestButton" Content="Test button" />
<telerik:RadDatePicker x:Name="startDate"
Style="{StaticResource VisitsReportTextBoxStyle}"
Foreground="#FFFFFF"
DateTimeWatermarkContent="Start Date"
SelectedDate="{TemplateBinding StartDateSelectedDate}"
/>
<telerik:RadDatePicker x:Name="endDate"
Style="{StaticResource VisitsReportTextBoxStyle}"
DateTimeWatermarkContent="End Date"
SelectedDate="{TemplateBinding EndDateSelectedDate}"
/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Some Explanations
A key problem your original code had was that it hadn't implemented the dependency properties correctly. Note that properties now use GetValue and SetValue, also that the property meta data is used to assign the default rather than attempting to set in in onapplytemplate.
With the properties correctly implemented the template binding should work and in fact we're done as far as getting what appears to be your original intent, hence I've left out any actual code in the event handlers.
Create constants in the code to hold the names of key template parts that you want to interact with, this allows for name changes to be less expensive to make.
Add TemplatePart attributes to the class to indicate the key elements that the code expects to find, what their names should be and what base type they are expected to have. This allows for a designer to re-template an existing control, as long the declared template parts are present somewher the control should function correctly even if its UI is radically altered.
If you need to attach event handlers for some elements create a field to hold a reference to the element and then create a property to wrap round it. The property setter should then detach and attach the event handlers as you see in the code.
Make sure bae.OnApplyTemplate is called in the override of OnApplyTemplate then as you can see its quite straight forward to assign the above created properties.
I don't have the RadDatePicker so I can't test, my only outstanding concern is where the DateTime? is the correct type for the SelectedDate property. Certainly if it is its an improvement over the Microsoft offering which seems to have drop the ball on this typical data entry requirement.
I may only guess that the problem is that for OnApplyTemplate method Implementers should always call the base implementation before their own implementation.
The other thing is that from your code it looks like it's better to use TemplateBinding(Archive)(V4) in the template xaml
<telerik:RadDatePicker SelectedDate={TemplateBinding StartDateSelectedDate}
Style="{StaticResource VisitsReportTextBoxStyle}"
Foreground="#FFFFFF" x:Name="startDate"
DateTimeWatermarkContent="Start Date"/>
I've adopted what appears to be the standard way of validating textboxes in WPF using the IDataErrorInfo interface and styles as shown below. However, how can I disable the Save button when the page becomes invalid? Is this done somehow through triggers?
Default Public ReadOnly Property Item(ByVal propertyName As String) As String Implements IDataErrorInfo.Item
Get
Dim valid As Boolean = True
If propertyName = "IncidentCategory" Then
valid = True
If Len(IncidentCategory) = 0 Then
valid = False
End If
If Not valid Then
Return "Incident category is required"
End If
End If
Return Nothing
End Get
End Property
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="3" />
<Setter Property="Height" Value="23" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
A couple of things:
First, I would recommend using the RoutedCommand ApplicationCommands.Save for implementing the handling of the save button.
If you haven't checked out the WPF Command model, you can get the scoop here.
<Button Content="Save" Command="Save">
Now, to implement the functionality, you can add a command binding to the Window/UserControl or to the Button itself:
<Button.CommandBindings>
<CommandBinding Command="Save"
Executed="Save_Executed" CanExecute="Save_CanExecute"/>
</Button.CommandBindings>
</Button>
Implement these in code behind:
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
private void Save_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
}
In Save_CanExecute, set e.CanExecute based on the validity of the binding on the text box.
If you want to implement using the MVVM (Model-View-ViewModel) design pattern, check out Josh Smith's post on CommandSinkBinding.
One final note: If you want the enable/disable to be updated as soon as the value in the TextBox is changed, set UpdateSourceTrigger="PropertyChanged" on the binding for the TextBox.
EDIT: If you want to validate/invalidate based on all of the bindings in the control, here are a few suggestions.
1) You are already implementing IDataErrorInfo. Try implementing the IDataErrorInfo.Error property such that it returns the string that is invalid for all of the properties that you are binding to. This will only work if your whole control is binding to a single data object. Set e.CanExecute = string.IsNullOrEmpty(data.Error);
2) Use reflection to get all of the public static DependencyProperties on the relevant controls. Then call BindingOperations.GetBindingExpression(relevantControl, DependencyProperty) in a loop on each property so you can test the validation.
3) In the constructor, manually create a collection of all bound properties on nested controls. In CanExecute, iterate through this collection and validate each DependencyObject/DepencyProperty combination by using BindingOperation.GetBindingExpression() to get expressions and then examining BindingExpression.HasError.
I've created attached property just for this:
public static class DataErrorInfoHelper
{
public static object GetDataErrorInfo(ButtonBase obj)
{
return (object)obj.GetValue(DataErrorInfoProperty);
}
public static void SetDataErrorInfo(ButtonBase obj, object value)
{
obj.SetValue(DataErrorInfoProperty, value);
}
// Using a DependencyProperty as the backing store for DataErrorInfo. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataErrorInfoProperty =
DependencyProperty.RegisterAttached("DataErrorInfo", typeof(object), typeof(DataErrorInfoHelper), new PropertyMetadata(null, OnDataErrorInfoChanged));
private static void OnDataErrorInfoChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var button = d as ButtonBase;
if (button.Tag == null)
button.Tag = new DataErrorInfoContext { Button = button };
var context = button.Tag as DataErrorInfoContext;
if(e.OldValue != null)
{
PropertyChangedEventManager.RemoveHandler(((INotifyPropertyChanged)e.OldValue), context.Handler, string.Empty);
}
var inotify = e.NewValue as INotifyPropertyChanged;
if (inotify != null)
{
PropertyChangedEventManager.AddHandler(inotify, context.Handler, string.Empty);
context.Handler(inotify, new PropertyChangedEventArgs(string.Empty));
}
}
private class DataErrorInfoContext
{
public ButtonBase Button { get; set; }
public void Handler(object sender, PropertyChangedEventArgs e)
{
var dei = sender as IDataErrorInfo;
foreach (var property in dei.GetType().GetProperties())
{
if (!string.IsNullOrEmpty(dei[property.Name]))
{
Button.IsEnabled = false;
return;
}
}
Button.IsEnabled = string.IsNullOrEmpty(dei.Error);
}
}
}
I'm using it like this on my forms:
<TextBlock Margin="2">e-mail:</TextBlock>
<TextBox Margin="2" Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
<!-- other databindings--->
<Button Margin="2" local:DataErrorInfoHelper.DataErrorInfo="{Binding}" Commands="{Binding SaveCommand}">Create account</Button>