Is there any time entry/picker control Available in WPF like Date picker?
Thanks
I could't find one on the web, so I created it from scratch. I don't completely understand Dependency Properties, so i'm omitted those for now. The control is a 12 hour time picker control. I'm new to WPF so I don't fully understand all the new language syntaxes but this control will do the trick for me in the project i'm creating at home.
It supports setting time as a DateTime or a TimeSpan.
Below I will paste the XAML and the Code-Behind.
The XAML
<UserControl x:Class="WorkDayManager3.WPF.UserControls.TimeControl"
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="35" d:DesignWidth="100">
<Border BorderBrush="LightBlue" BorderThickness="1" Margin="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="5" />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox x:Name="txtHours" BorderThickness="0" MaxLength="2" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" Text="1" KeyUp="txt_KeyUp" MouseWheel="txt_MouseWheel" PreviewKeyUp="txt_PreviewKeyUp" />
<TextBlock Text=":" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBox x:Name="txtMinutes" BorderThickness="0" MaxLength="2" TextAlignment="Center" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" Text="00" KeyUp="txt_KeyUp" MouseWheel="txt_MouseWheel" PreviewKeyUp="txt_PreviewKeyUp" />
<TextBox x:Name="txtAmPm" BorderThickness="0" MaxLength="2" TextAlignment="Center" Grid.Column="3" HorizontalAlignment="Left" VerticalAlignment="Center" PreviewTextInput="txtAmPm_PreviewTextInput" Text="AM" KeyUp="txt_KeyUp" MouseWheel="txt_MouseWheel" Padding="0, 0, 3, 0" />
<Grid Grid.Column="4">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button x:Name="btnUp" Focusable="False" Click="btnUp_Click">
<TextBlock Text="p" FontFamily="Wingdings 3" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Button>
<Button x:Name="btnDown" Grid.Row="1" Focusable="False" Click="btnDown_Click">
<TextBlock Text="q" FontFamily="Wingdings 3" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Button>
</Grid>
</Grid>
</Border>
</UserControl>
The Code-Behind
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WorkDayManager3.WPF.UserControls
{
/// <summary>
/// Interaction logic for TimeControl.xaml
/// </summary>
public partial class TimeControl : UserControl
{
public TimeControl()
{
InitializeComponent();
}
#region Properties
/// <summary>
/// Gets or sets the date time value.
/// </summary>
/// <value>The date time value.</value>
public DateTime? DateTimeValue
{
get
{
string hours = this.txtHours.Text;
string minutes = this.txtMinutes.Text;
string amPm = this.txtAmPm.Text;
if (!string.IsNullOrWhiteSpace(hours)
&& !string.IsNullOrWhiteSpace(minutes)
&& !string.IsNullOrWhiteSpace(amPm))
{
string value = string.Format("{0}:{1} {2}", this.txtHours.Text, this.txtMinutes.Text, this.txtAmPm.Text);
DateTime time = DateTime.Parse(value);
return time;
}
else
{
return null;
}
}
set
{
DateTime? time = value;
if (time.HasValue)
{
string timeString = time.Value.ToShortTimeString();
//9:54 AM
string[] values = timeString.Split(':', ' ');
if (values.Length == 3)
{
this.txtHours.Text = values[0];
this.txtMinutes.Text = values[1];
this.txtAmPm.Text = values[2];
}
}
}
}
/// <summary>
/// Gets or sets the time span value.
/// </summary>
/// <value>The time span value.</value>
public TimeSpan? TimeSpanValue
{
get
{
DateTime? time = this.DateTimeValue;
if (time.HasValue)
{
return new TimeSpan(time.Value.Ticks);
}
else
{
return null;
}
}
set
{
TimeSpan? timeSpan = value;
if (timeSpan.HasValue)
{
this.DateTimeValue = new DateTime(timeSpan.Value.Ticks);
}
}
}
#endregion
#region Event Subscriptions
/// <summary>
/// Handles the Click event of the btnDown control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
private void btnDown_Click(object sender, RoutedEventArgs e)
{
string controlId = this.GetControlWithFocus().Name;
if ("txtHours".Equals(controlId))
{
this.ChangeHours(false);
}
else if ("txtMinutes".Equals(controlId))
{
this.ChangeMinutes(false);
}
else if ("txtAmPm".Equals(controlId))
{
this.ToggleAmPm();
}
}
/// <summary>
/// Handles the Click event of the btnUp control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
private void btnUp_Click(object sender, RoutedEventArgs e)
{
string controlId = this.GetControlWithFocus().Name;
if ("txtHours".Equals(controlId))
{
this.ChangeHours(true);
}
else if ("txtMinutes".Equals(controlId))
{
this.ChangeMinutes(true);
}
else if ("txtAmPm".Equals(controlId))
{
this.ToggleAmPm();
}
}
/// <summary>
/// Handles the PreviewTextInput event of the txtAmPm control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.TextCompositionEventArgs"/> instance containing the event data.</param>
private void txtAmPm_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
// prevent users to type text
e.Handled = true;
}
/// <summary>
/// Handles the KeyUp event of the txt control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> instance containing the event data.</param>
private void txt_KeyUp(object sender, KeyEventArgs e)
{
// check for up and down keyboard presses
if (Key.Up.Equals(e.Key))
{
btnUp_Click(this, null);
}
else if (Key.Down.Equals(e.Key))
{
btnDown_Click(this, null);
}
}
/// <summary>
/// Handles the MouseWheel event of the txt control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseWheelEventArgs"/> instance containing the event data.</param>
private void txt_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0)
{
btnUp_Click(this, null);
}
else
{
btnDown_Click(this, null);
}
}
/// <summary>
/// Handles the PreviewKeyUp event of the txt control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> instance containing the event data.</param>
private void txt_PreviewKeyUp(object sender, KeyEventArgs e)
{
TextBox textBox = (TextBox)sender;
// make sure all characters are number
bool allNumbers = textBox.Text.All(Char.IsNumber);
if (!allNumbers)
{
e.Handled = true;
return;
}
// make sure user did not enter values out of range
int value;
int.TryParse(textBox.Text, out value);
if ("txtHours".Equals(textBox.Name) && value > 12)
{
EnforceLimits(e, textBox);
}
else if ("txtMinutes".Equals(textBox.Name) && value > 59)
{
EnforceLimits(e, textBox);
}
}
#endregion
#region Methods
/// <summary>
/// Changes the hours.
/// </summary>
/// <param name="isUp">if set to <c>true</c> [is up].</param>
private void ChangeHours(bool isUp)
{
int value = Convert.ToInt32(this.txtHours.Text);
if (isUp)
{
value += 1;
if (value == 13)
{
value = 1;
}
}
else
{
value -= 1;
if (value == 0)
{
value = 12;
}
}
this.txtHours.Text = Convert.ToString(value);
}
/// <summary>
/// Changes the minutes.
/// </summary>
/// <param name="isUp">if set to <c>true</c> [is up].</param>
private void ChangeMinutes(bool isUp)
{
int value = Convert.ToInt32(this.txtMinutes.Text);
if (isUp)
{
value += 1;
if (value == 60)
{
value = 0;
}
}
else
{
value -= 1;
if (value == -1)
{
value = 59;
}
}
string textValue = Convert.ToString(value);
if (value < 10)
{
textValue = "0" + Convert.ToString(value);
}
this.txtMinutes.Text = textValue;
}
/// <summary>
/// Enforces the limits.
/// </summary>
/// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> instance containing the event data.</param>
/// <param name="textBox">The text box.</param>
/// <param name="enteredValue">The entered value.</param>
private static void EnforceLimits(KeyEventArgs e, TextBox textBox)
{
string enteredValue = GetEnteredValue(e.Key);
string text = textBox.Text.Replace(enteredValue, "");
if (string.IsNullOrEmpty(text))
{
text = enteredValue;
}
textBox.Text = text;
e.Handled = true;
}
/// <summary>
/// Gets the control with focus.
/// </summary>
/// <returns></returns>
private TextBox GetControlWithFocus()
{
TextBox txt = new TextBox();
if (this.txtHours.IsFocused)
{
txt = this.txtHours;
}
else if (this.txtMinutes.IsFocused)
{
txt = this.txtMinutes;
}
else if (this.txtAmPm.IsFocused)
{
txt = this.txtAmPm;
}
return txt;
}
/// <summary>
/// Gets the entered value.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
private static string GetEnteredValue(Key key)
{
string value = string.Empty;
switch (key)
{
case Key.D0:
case Key.NumPad0:
value = "0";
break;
case Key.D1:
case Key.NumPad1:
value = "1";
break;
case Key.D2:
case Key.NumPad2:
value = "2";
break;
case Key.D3:
case Key.NumPad3:
value = "3";
break;
case Key.D4:
case Key.NumPad4:
value = "4";
break;
case Key.D5:
case Key.NumPad5:
value = "5";
break;
case Key.D6:
case Key.NumPad6:
value = "6";
break;
case Key.D7:
case Key.NumPad7:
value = "7";
break;
case Key.D8:
case Key.NumPad8:
value = "8";
break;
case Key.D9:
case Key.NumPad9:
value = "9";
break;
}
return value;
}
/// <summary>
/// Toggles the am pm.
/// </summary>
private void ToggleAmPm()
{
if ("AM".Equals(this.txtAmPm.Text))
{
this.txtAmPm.Text = "PM";
}
else
{
this.txtAmPm.Text = "AM";
}
}
#endregion
}
}
That's the control, feel free to make modifications as needed. It's not perfect but it's better than other controls i've found
Related
I have to validate some Properties, before I try to save my Entity into Database.
Problem:
Programmstart: Validation error is shown of textbox in tabitem1
User select: Tabitem2 in the View
User select: Tabitem1 in the View
Validation error of empty textbox is not shown in tabitem1 anymore.
Excepted behavior:
Validation error shall be shown every time also if the user changes the selected tabitem.
Tools / Frameworks used:
Prism 6.3 (New Project with Templatepack PrismUnity
Prism.Validation (https://github.com/mfe-/Prism.Validation)
Questions:
Why the DataAnnoation is not shown anymore after selection between the different tabitems? The ViewModel property hasErrors is true.
How I can restart the evaluation, if the user selected tabitem1 again?
View:
<Window x:Class="PrismUnityApp1TestValidation.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="525">
<StackPanel>
<!--<ContentControl prism:RegionManager.RegionName="ContentRegion" />-->
<TabControl>
<TabItem>
<TabItem.Content>
<TextBox Height="50" Text="{Binding TestText, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
</TabItem.Content>
</TabItem>
<TabItem>
<TabItem.Content>
<TextBlock Text="TabItem2"></TextBlock>
</TabItem.Content>
</TabItem>
</TabControl>
</StackPanel>
</Window>
ViewModel:
using System.ComponentModel.DataAnnotations;
using Prism.Mvvm;
using Prism.Validation;
namespace PrismUnityApp1TestValidation.ViewModels
{
public class MainWindowViewModel : ValidatableBindableBase
{
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _testtext;
[Required]
public string TestText
{
get { return _testtext; }
set { SetProperty(ref _testtext, value); }
}
public MainWindowViewModel()
{
}
}
}
ValidatableBindableBase (NugetPackage Prism.Validation):
namespace Prism.Validation
{
/// <summary>
/// The IValidatableBindableBase interface was created to add validation support for model classes that contain validation rules.
/// The default implementation of IValidatableBindableBase is the ValidatableBindableBase class, which contains the logic to run the validation rules of the
/// instance of a model class and return the results of this validation as a list of properties' errors.
/// </summary>
// Documentation on validating user input is at http://go.microsoft.com/fwlink/?LinkID=288817&clcid=0x409
public class ValidatableBindableBase : BindableBase, IValidatableBindableBase, INotifyDataErrorInfo
{
private readonly BindableValidator _bindableValidator;
/// <summary>
/// Initializes a new instance of the <see cref="ValidatableBindableBase"/> class.
/// </summary>
public ValidatableBindableBase()
{
_bindableValidator = new BindableValidator(this);
}
/// <summary>
/// Gets or sets a value indicating whether this instance is validation enabled.
/// </summary>
/// <value>
/// <c>true</c> if validation is enabled for this instance; otherwise, <c>false</c>.
/// </value>
public bool IsValidationEnabled
{
get { return _bindableValidator.IsValidationEnabled; }
set { _bindableValidator.IsValidationEnabled = value; }
}
/// <summary>
/// Returns the BindableValidator instance that has an indexer property.
/// </summary>
/// <value>
/// The Bindable Validator Indexer property.
/// </value>
public BindableValidator Errors
{
get
{
return _bindableValidator;
}
}
/// <summary>
/// Gets a value that indicates whether the entity has validation errors.
/// </summary>
/// <value>
/// <c>true</c> if this instance contains validation errors; otherwise, <c>false</c>.
/// </value>
public bool HasErrors
{
get
{
return !ValidateProperties();
}
}
/// <summary>
/// Occurs when the Errors collection changed because new errors were added or old errors were fixed.
/// </summary>
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged
{
add { _bindableValidator.ErrorsChanged += value; }
remove { _bindableValidator.ErrorsChanged -= value; }
}
/// <summary>
/// Gets all errors.
/// </summary>
/// <returns> A ReadOnlyDictionary that's key is a property name and the value is a ReadOnlyCollection of the error strings.</returns>
public ReadOnlyDictionary<string, ReadOnlyCollection<string>> GetAllErrors()
{
return _bindableValidator.GetAllErrors();
}
/// <summary>
/// Validates the properties of the current instance.
/// </summary>
/// <returns>
/// Returns <c>true</c> if all properties pass the validation rules; otherwise, false.
/// </returns>
public bool ValidateProperties()
{
return _bindableValidator.ValidateProperties();
}
/// <summary>
/// Validates a single property with the given name of the current instance.
/// </summary>
/// <param name="propertyName">The property to be validated.</param>
/// <returns>Returns <c>true</c> if the property passes the validation rules; otherwise, false.</returns>
public bool ValidateProperty(string propertyName)
{
return !_bindableValidator.IsValidationEnabled // don't fail if validation is disabled
|| _bindableValidator.ValidateProperty(propertyName);
}
/// <summary>
/// Sets the error collection of this instance.
/// </summary>
/// <param name="entityErrors">The entity errors.</param>
public void SetAllErrors(IDictionary<string, ReadOnlyCollection<string>> entityErrors)
{
_bindableValidator.SetAllErrors(entityErrors);
}
/// <summary>
/// Checks if a property already matches a desired value. Sets the property and
/// notifies listeners only when necessary. We are overriding this property to ensure that the SetProperty and the ValidateProperty methods are fired in a
/// deterministic way.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <returns>
/// True if the value was changed, false if the existing value matched the
/// desired value.
/// </returns>
protected override bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
var result = base.SetProperty(ref storage, value, propertyName);
if (result && !string.IsNullOrEmpty(propertyName))
{
if (_bindableValidator.IsValidationEnabled)
{
_bindableValidator.ValidateProperty(propertyName);
}
}
return result;
}
/// <summary>
/// Gets the validation errors for a specified property or for the entire entity.
/// </summary>
/// <param name="propertyName">The name of the property to retrieve validation errors for; or null or Empty, to retrieve entity-level errors.</param>
/// <returns>The validation errors for the property or entity.</returns>
public IEnumerable GetErrors(string propertyName)
{
if (HasErrors==false)
{
return Enumerable.Empty<String>();
}
return _bindableValidator[propertyName];
}
}
}
I found a solution here:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/fb50537c-feec-42dc-8439-dcf78ef8951a/validation-error-and-tab-control?forum=wpf
I changed as mentioned in the post my View.
View:
<Window x:Class="PrismUnityApp1TestValidation.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="525">
<StackPanel>
<!--<ContentControl prism:RegionManager.RegionName="ContentRegion" />-->
<TabControl>
<TabItem IsSelected="{Binding TabItem1Selected}">
<TabItem.Content>
<AdornerDecorator>
<TextBox Height="50" Text="{Binding TestText, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
</AdornerDecorator>
</TabItem.Content>
</TabItem>
<TabItem>
<TabItem.Content>
<TextBlock Text="TabItem2"></TextBlock>
</TabItem.Content>
</TabItem>
</TabControl>
</StackPanel>
</Window>
I have a ListBox with my menu items in it.
<ListBox x:Name="ListBoxMenu" SelectionChanged="ListBoxMenu_SelectionChanged"
Grid.Column="0" Margin="0" Padding="0" Grid.Row="1" Width="{StaticResource LeftMenuWidth}"
ItemsSource="{Binding MenuItems}"
Background="{StaticResource ListBoxColor}"
BorderThickness="0"
SelectedIndex="0" VerticalAlignment="Stretch" >
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Image Source="{Binding MenuImage}" Height="20" Width="20" DockPanel.Dock="Left" Margin="5" />
<TextBlock Text="{Binding MenuName}" FontSize="{StaticResource MenuFontSize}" FontWeight="Bold" DockPanel.Dock="Left" Width="Auto" VerticalAlignment="Center" HorizontalAlignment="Left"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I kind of chopped up the code hope it still make since.
Then I have a control template that loads each user control.
<ContentControl Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}" Grid.Column="1" Grid.Row="1"/>
The Problem:
My problem is that I would like to test when a user leaves the user control if they have made any changes to ask them to save the changes. I already have NotifyPropertyChange working so that isn't the problem. I need to figure out how to see when the user is leaving the control / page.
What I have tried
As you can see I have added selectionchanged to the list box which technically does work however its not ideal because the usercontrol changes visually then the user is prompted to save any changes. I want to prompt them before they leave the user control.
SelectionChanged="ListBoxMenu_SelectionChanged"
Updates #1
There is more than one possible helpful suggestions to handle the "from view navigation", here are a couple of simple examples:
In order to check if your control is active(I mean is shown to user), when there is no any navigation controllers in use, I think you can use the control's IsVisibleChanged event that indicates the control's IsVisible(true/false) state. In case you want to start the IsDirty logic when your control is partially visible you can use the #Evk guy suggestion(using of LostFocus), test the IsHitTestVisible on the control bounds and according to the test result(how more the control is hidden) you can start(or not) your desired logic.
Here is an example of IsHitTest visibilty(from this link)
/// <summary>
/// helps to indicate the partial IsVisible state
/// </summary>
/// <param name="element">elemnt under the test</param>
/// <param name="container">parent window or control</param>
/// <returns></returns>
private bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
if (!element.IsVisible)
return false;
Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}
In case you have a navigation supporting control(something like a Frame) you can use its events to know that the navigation is started(aka you are going to move to another control), like FragmentNavigation.
In addition you should implement an IsDirty on your ViewModel. Here are a few examples of how to do that:
MVVM - implementing 'IsDirty' functionality to a ModelView in order to save data
Almost-automatic INotifyPropertyChanged, automatic IsDirty, and automatic ChangeTracking.
Here is some code sample for an IsDirty implementation(all credit to this guy)
/// <summary>
/// Provides a base class for objects that support property change notification
/// and querying for changes and resetting of the changed status.
/// </summary>
public abstract class ViewModelBase : IChangeTracking, INotifyPropertyChanged
{
//========================================================
// Constructors
//========================================================
#region ViewModelBase()
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
/// </summary>
protected ViewModelBase()
{
this.PropertyChanged += new PropertyChangedEventHandler(OnNotifiedOfPropertyChanged);
}
#endregion
//========================================================
// Private Methods
//========================================================
#region OnNotifiedOfPropertyChanged(object sender, PropertyChangedEventArgs e)
/// <summary>
/// Handles the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for this object.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="PropertyChangedEventArgs"/> that contains the event data.</param>
private void OnNotifiedOfPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e != null && !String.Equals(e.PropertyName, "IsChanged", StringComparison.Ordinal))
{
this.IsChanged = true;
}
}
#endregion
//========================================================
// IChangeTracking Implementation
//========================================================
#region IsChanged
/// <summary>
/// Gets the object's changed status.
/// </summary>
/// <value>
/// <see langword="true"/> if the object’s content has changed since the last call to <see cref="AcceptChanges()"/>; otherwise, <see langword="false"/>.
/// The initial value is <see langword="false"/>.
/// </value>
public bool IsChanged
{
get
{
lock (_notifyingObjectIsChangedSyncRoot)
{
return _notifyingObjectIsChanged;
}
}
protected set
{
lock (_notifyingObjectIsChangedSyncRoot)
{
if (!Boolean.Equals(_notifyingObjectIsChanged, value))
{
_notifyingObjectIsChanged = value;
this.OnPropertyChanged("IsChanged");
}
}
}
}
private bool _notifyingObjectIsChanged;
private readonly object _notifyingObjectIsChangedSyncRoot = new Object();
#endregion
#region AcceptChanges()
/// <summary>
/// Resets the object’s state to unchanged by accepting the modifications.
/// </summary>
public void AcceptChanges()
{
this.IsChanged = false;
}
#endregion
//========================================================
// INotifyPropertyChanged Implementation
//========================================================
#region PropertyChanged
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region OnPropertyChanged(PropertyChangedEventArgs e)
/// <summary>
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event.
/// </summary>
/// <param name="e">A <see cref="PropertyChangedEventArgs"/> that provides data for the event.</param>
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
#endregion
#region OnPropertyChanged(string propertyName)
/// <summary>
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for the specified <paramref name="propertyName"/>.
/// </summary>
/// <param name="propertyName">The <see cref="MemberInfo.Name"/> of the property whose value has changed.</param>
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
#endregion
#region OnPropertyChanged(params string[] propertyNames)
/// <summary>
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for the specified <paramref name="propertyNames"/>.
/// </summary>
/// <param name="propertyNames">An <see cref="Array"/> of <see cref="String"/> objects that contains the names of the properties whose values have changed.</param>
/// <exception cref="ArgumentNullException">The <paramref name="propertyNames"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
protected void OnPropertyChanged(params string[] propertyNames)
{
if (propertyNames == null)
{
throw new ArgumentNullException("propertyNames");
}
foreach (var propertyName in propertyNames)
{
this.OnPropertyChanged(propertyName);
}
}
#endregion
}
Let me know if you want more examples or code.
I'm building a wpf application with a custom control and everything worked so far.
But now i encountered two problems:
I want to assign a background color to my control but that overlays the rectangle inside the grid, so the rectangle becomes invisible.
I tried to write a template for a ContentControl but the content does not render as expected, meaning only the display name does show up with the text of each progress bar.
The template for my custom control (if the code behind is of interest i'll add that as well):
<Style TargetType="{x:Type local:MetroProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MetroProgressBar}">
<Grid Background="{TemplateBinding Background}">
<Rectangle Fill="{TemplateBinding ProgressBrush}" HorizontalAlignment="Left"
VerticalAlignment="Stretch" Width="{TemplateBinding ProgressBarWidth}"
Visibility="{TemplateBinding IsHorizontal, Converter={StaticResource BoolToVis}}"/>
<Rectangle Fill="{TemplateBinding ProgressBrush}" HorizontalAlignment="Stretch"
VerticalAlignment="Bottom" Height="{TemplateBinding ProgressBarHeight}"
Visibility="{TemplateBinding IsVertical, Converter={StaticResource BoolToVis}}"/>
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"/>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center"
Text="{TemplateBinding Text}"
FontSize="{TemplateBinding FontSize}" FontStyle="{TemplateBinding FontStyle}" FontWeight="{TemplateBinding FontWeight}"
FontFamily="{TemplateBinding FontFamily}" FontStretch="{TemplateBinding FontStretch}"
Foreground="{TemplateBinding Foreground}" TextWrapping="Wrap"/>
<Polygon Fill="{TemplateBinding BorderBrush}" Points="{TemplateBinding LeftBorderTriangle}"/>
<Polygon Fill="{TemplateBinding BorderBrush}" Points="{TemplateBinding RightBorderTriangle}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The template for the ContentControl:
<vm:RamViewModel x:Key="RamInformationSource"/>
<Style TargetType="ContentControl" x:Key="MemoryUsageTemplate">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid DataContext="{Binding Source={StaticResource RamInformationSource}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Center" Text="{Binding DisplayName}" VerticalAlignment="Center"
FontSize="15"/>
<ctrl:MetroProgressBar Grid.Column="1" VerticalAlignment="Stretch" Width="55" Orientation="Vertical" HorizontalAlignment="Center"
ExtenedBorderWidth="0.25" BorderBrush="Gray" Text="Available memory" Progress="{Binding AvailableMemory}"
MaxValue="{Binding TotalMemory}"/>
<ctrl:MetroProgressBar Grid.Column="2" VerticalAlignment="Stretch" Width="60" Orientation="Vertical" HorizontalAlignment="Center"
ExtenedBorderWidth="0.2" BorderBrush="Black" Text="Total memory" Progress="100"
MaxValue="{Binding TotalMemory}"/>
<ctrl:MetroProgressBar Grid.Column="3" VerticalAlignment="Stretch" Width="60" Orientation="Vertical" HorizontalAlignment="Center"
ExtenedBorderWidth="0.2" BorderBrush="DodgerBlue" Text="Used memory" Progress="{Binding UsedMemory}"
MaxValue="{Binding TotalMemory}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The xaml that displays the content:
...
<ContentControl Style="{StaticResource MemoryUsageTemplate}"/>
<ctrl:MetroProgressBar Grid.Row="1" BorderBrush="Black" Text="Test" HorizontalAlignment="Left" Background="Aquamarine"
Orientation="Horizontal" BorderThickness="2" Height="50" Width="200" Progress="46"/>
<ctrl:MetroProgressBar Grid.Row="1" BorderBrush="Black" Text="Test" HorizontalAlignment="Right"
Orientation="Horizontal" BorderThickness="2" Height="50" Width="200" Progress="46"/>
...
Top of the image shows the content control with the template applied. The bottom shows the two progress bars as defined in the last xaml (left with background, right without).
That are all custom DPs that are defined for the control:
/// <summary>
/// Identifies the ExtenedBorderWidth property.
/// </summary>
public static readonly DependencyProperty ExtenedBorderWidthProperty =
DependencyProperty.Register("ExtenedBorderWidth", typeof(double), typeof(MetroProgressBar),
new PropertyMetadata(0.17, ExtendedBorderWidthValueChanged));
/// <summary>
/// Identifies the Text property.
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MetroProgressBar), new PropertyMetadata(""));
/// <summary>
/// Identifies the Orientation property.
/// </summary>
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(MetroProgressBar), new PropertyMetadata(Orientation.Horizontal, OrientationValueChanged));
/// <summary>
/// Identifies the IsHorizontal property.
/// </summary>
public static readonly DependencyProperty IsHorizontalProperty =
DependencyProperty.Register("IsHorizontal", typeof(bool), typeof(MetroProgressBar), new PropertyMetadata(true, OrientationChangedByProperty));
/// <summary>
/// Identifies the IsVertical property.
/// </summary>
public static readonly DependencyProperty IsVerticalProperty =
DependencyProperty.Register("IsVertical", typeof(bool), typeof(MetroProgressBar), new PropertyMetadata(false, OrientationChangedByProperty));
/// <summary>
/// Identifies the ProgressBrush property.
/// </summary>
public static readonly DependencyProperty ProgressBrushProperty =
DependencyProperty.Register("ProgressBrush", typeof(Brush), typeof(MetroProgressBar), new PropertyMetadata(new SolidColorBrush(Colors.LightGreen)));
/// <summary>
/// Identifies the LeftBorderTriangle property.
/// </summary>
public static readonly DependencyProperty LeftBorderTriangleProperty =
DependencyProperty.Register("LeftBorderTriangle", typeof(PointCollection), typeof(MetroProgressBar), new PropertyMetadata(new PointCollection()));
/// <summary>
/// Identifies the RightBorderTriangle property.
/// </summary>
public static readonly DependencyProperty RightBorderTriangleProperty =
DependencyProperty.Register("RightBorderTriangle", typeof(PointCollection), typeof(MetroProgressBar), new PropertyMetadata(new PointCollection()));
/// <summary>
/// Identifies the MaxValue property.
/// </summary>
public static readonly DependencyProperty MaxValueProperty =
DependencyProperty.Register("MaxValue", typeof(ulong), typeof(MetroProgressBar), new PropertyMetadata(100UL, MaxValueChanged));
/// <summary>
/// Identifies the Progress property.
/// </summary>
public static readonly DependencyProperty ProgressProperty =
DependencyProperty.Register("Progress", typeof(double), typeof(MetroProgressBar), new PropertyMetadata(0.0d, ProgressValueChanged));
/// <summary>
/// Identifies the ProgressBarWidth property.
/// </summary>
public static readonly DependencyProperty ProgressBarWidthProperty
= DependencyProperty.Register("ProgressBarWidth", typeof(double), typeof(MetroProgressBar), new PropertyMetadata(0.0d));
/// <summary>
/// Identifies the ProgressBarHeight property.
/// </summary>
public static readonly DependencyProperty ProgressBarHeightProperty
= DependencyProperty.Register("ProgressBarHeight", typeof(double), typeof(MetroProgressBar), new PropertyMetadata(0.0d));
The DP value changed callbacks and instance methods:
#region Static
/// <summary>
/// Changes the orientation based on the calling property.
/// </summary>
/// <param name="source">The source of the event.</param>
/// <param name="e">Event information.</param>
private static void OrientationChangedByProperty(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
//lock (lockOrientationByProperty)
{
MetroProgressBar pb = source as MetroProgressBar;
if (e.Property == IsVerticalProperty)
{
if ((bool)e.NewValue)
{
pb.IsHorizontal = false;
pb.Orientation = Orientation.Vertical;
}
else
{
pb.IsHorizontal = true;
pb.Orientation = Orientation.Horizontal;
}
}
else
{
// IsVerticalProperty is property that changed
if (!(bool)e.NewValue)
{
pb.IsHorizontal = false;
pb.Orientation = Orientation.Vertical;
}
else
{
pb.IsHorizontal = true;
pb.Orientation = Orientation.Horizontal;
}
}
AdjustVisibleProgressRect(pb);
}
}
/// <summary>
/// Sets the progress value to the new maximum value, if the new max value is less than
/// the current progress value.
/// </summary>
/// <param name="source">The source of the event.</param>
/// <param name="e">Event information.</param>
private static void MaxValueChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
//lock (lockMaxValue)
{
MetroProgressBar pb = source as MetroProgressBar;
ulong val = Convert.ToUInt64(e.NewValue);
if (val < Convert.ToUInt64(pb.Progress))
{
pb.Progress = val;
// Raise finished event
pb.OnFinished(EventArgs.Empty);
}
}
}
/// <summary>
/// Changes the width of the progress indication rectangle of the progress bar.
/// </summary>
/// <param name="source">The source of the event.</param>
/// <param name="e">Event information.</param>
private static void ProgressValueChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
//lock (lockProgress)
{
MetroProgressBar pb = source as MetroProgressBar;
AdjustVisibleProgressRect(pb, (double)e.NewValue);
pb.OnProgressChanged(new ProgressChangedEventArgs((double)e.NewValue));
// If new progress value equals or is greater than max value raise the finished event
if (pb.MaxValue <= Convert.ToUInt64(e.NewValue))
pb.OnFinished(EventArgs.Empty);
}
}
/// <summary>
/// Changes the width of the progress indication rectangle of the progress bar.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">Event information.</param>
private static void OrientationValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
//lock (lockOrientation)
{
MetroProgressBar pb = sender as MetroProgressBar;
pb.AdjustToOrientationChange();
if (pb.Orientation == Orientation.Horizontal)
{
pb.IsVertical = false;
pb.IsHorizontal = true;
}
else
{
pb.IsVertical = true;
pb.IsHorizontal = false;
}
pb.OnOrientationChanged(new OrientationChangedEventArgs((Orientation)e.OldValue, (Orientation)e.NewValue));
}
}
/// <summary>
/// Causes the progress bar to reassign the extended border.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">Event information.</param>
private static void ExtendedBorderWidthValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
//lock (lockExtendedBorder)
{
MetroProgressBar pb = sender as MetroProgressBar;
pb.SetUpBorderParts();
}
}
/// <summary>
/// Adjusts the progress bars visible progress rectangles after progress or visible changes.
/// </summary>
/// <param name="pb">The progress bar that changed.</param>
/// <param name="newValue">The new progress value. Only has to be set if there has been a progress change.</param>
private static void AdjustVisibleProgressRect(MetroProgressBar pb, double newValue = -1)
{
if (pb.Orientation == Orientation.Horizontal)
{
pb.ProgressBarWidth = (newValue == -1 ? pb.Progress : newValue) / pb.MaxValue * pb.Width;
}
else
{
pb.ProgressBarHeight = (newValue == -1 ? pb.Progress : newValue) / pb.MaxValue * pb.Height;
}
}
#endregion
#region Non-Static
/// <summary>
/// Adjusts the border ornaments to the new orientation of the control.
/// </summary>
private void AdjustToOrientationChange()
{
SetUpBorderParts();
}
/// <summary>
/// Sets up the triangles that are placed on the left and right side of the progress bar.
/// </summary>
private void SetUpBorderParts()
{
PointCollection leftBorder = new PointCollection();
PointCollection rightBorder = new PointCollection();
double borderWidth = ExtenedBorderWidth;
if (Orientation == Orientation.Horizontal)
{
// Left triangle
leftBorder.Add(new Point(0, 0));
leftBorder.Add(new Point(0, Height));
leftBorder.Add(new Point(Width * borderWidth, 0));
// Right triangle
rightBorder.Add(new Point(Width, 0));
rightBorder.Add(new Point(Width, Height));
rightBorder.Add(new Point(Width - (Width * borderWidth), Height));
}
else
{
// Top border
leftBorder.Add(new Point(0, 0));
leftBorder.Add(new Point(Width, 0));
leftBorder.Add(new Point(0, Height * borderWidth));
// Bottom border
rightBorder.Add(new Point(0, Height));
rightBorder.Add(new Point(Width, Height));
rightBorder.Add(new Point(Width, Height - (Height * borderWidth)));
}
LeftBorderTriangle = leftBorder;
RightBorderTriangle = rightBorder;
}
/// <summary>
/// Raises the Fnished event.
/// </summary>
/// <param name="e">Information on the event.</param>
protected virtual void OnFinished(EventArgs e)
{
EventHandler handler = finished;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Raises the ProgressChanged event.
/// </summary>
/// <param name="e">Information on the event.</param>
protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
EventHandler<ProgressChangedEventArgs> handler = progressChanged;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Raises the OrientationChanged event.
/// </summary>
/// <param name="e">Information on the event.</param>
protected virtual void OnOrientationChanged(OrientationChangedEventArgs e)
{
EventHandler<OrientationChangedEventArgs> handler = orientationChanged;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Raises the RenderSizeChanged event and sets up the border parts.
/// </summary>
/// <param name="sizeInfo">Info on the size change.</param>
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
SetUpBorderParts();
AdjustVisibleProgressRect(this);
}
#endregion
I found the answer to my first problem... Basically the Border element of the progress bar had its Background-property bound to the Control-background and since it is after the Rectangle in the visual tree it overlayed both of them.
The second problem occurred because i used Height and Width instead of ActualHeight and ActualWidth in the code of the user control. So when working with e.g. HorizontalAlignment.Stretch the Width/Height properties are not set and therefore all calculations based on them do not work.
I want a TextBox with a WaterMark text. I use this solution, which works fine as provided.
Since I have a couple TextBoxes in the control I want to make it a bit dynamic. So I use ( the first time) an attached property, but I can't make it work. No compile errors, but in XAML the Tag Statement can not be resolved ... Content={Binding Path=view:SomeClass.Tag, RelativeSource=...
What is wrong here?
I did this in XAML
<StackPanel Grid.Row="1" TextBlock.FontSize="12">
<TextBox Style="{DynamicResource TextBoxWaterMark}" view:SomeClass.Tag="Search" />
</StackPanel>
Style
<RibbonWindow.Resources>
<Style xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Key="TextBoxWaterMark"
TargetType="{x:Type TextBox}">
<Style.Resources>
<VisualBrush x:Key="CueBannerBrush"
AlignmentX="Left"
AlignmentY="Center"
Stretch="None">
<VisualBrush.Visual>
<Label Content="{Binding Path=view:SomeClass.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type view:SomeClass}}}" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsMouseCaptured" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</RibbonWindow.Resources>
DependencyObject
public static class SomeClass
{
public static readonly DependencyProperty TagProperty = DependencyProperty.RegisterAttached(
"Tag",
typeof(object),
typeof(SomeClass),
new FrameworkPropertyMetadata(null));
public static object GetTag(DependencyObject dependencyObject)
{
return dependencyObject.GetValue(TagProperty);
}
public static void SetTag(DependencyObject dependencyObject, object value)
{
dependencyObject.SetValue(TagProperty, value);
}
}
You Can create such type attached property. here see how.
Mostly peoples looking for watermask textboxs what about combobo items controls etc. lets cover these all at once.
create AttachedProperty like .
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
/// <summary>
/// Class that provides the Watermark attached property
/// </summary>
public static class WatermarkService
{
/// <summary>
/// Watermark Attached Dependency Property
/// </summary>
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.RegisterAttached(
"Watermark",
typeof(object),
typeof(WatermarkService),
new FrameworkPropertyMetadata((object)null, new PropertyChangedCallback(OnWatermarkChanged)));
#region Private Fields
/// <summary>
/// Dictionary of ItemsControls
/// </summary>
private static readonly Dictionary<object, ItemsControl> itemsControls = new Dictionary<object, ItemsControl>();
#endregion
/// <summary>
/// Gets the Watermark property. This dependency property indicates the watermark for the control.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to get the property from</param>
/// <returns>The value of the Watermark property</returns>
public static object GetWatermark(DependencyObject d)
{
return (object)d.GetValue(WatermarkProperty);
}
/// <summary>
/// Sets the Watermark property. This dependency property indicates the watermark for the control.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to set the property on</param>
/// <param name="value">value of the property</param>
public static void SetWatermark(DependencyObject d, object value)
{
d.SetValue(WatermarkProperty, value);
}
/// <summary>
/// Handles changes to the Watermark property.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> that fired the event</param>
/// <param name="e">A <see cref="DependencyPropertyChangedEventArgs"/> that contains the event data.</param>
private static void OnWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Control control = (Control)d;
control.Loaded += Control_Loaded;
if (d is ComboBox || d is TextBox)
{
control.GotKeyboardFocus += Control_GotKeyboardFocus;
control.LostKeyboardFocus += Control_Loaded;
}
if (d is ItemsControl && !(d is ComboBox))
{
ItemsControl i = (ItemsControl)d;
// for Items property
i.ItemContainerGenerator.ItemsChanged += ItemsChanged;
itemsControls.Add(i.ItemContainerGenerator, i);
// for ItemsSource property
DependencyPropertyDescriptor prop = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, i.GetType());
prop.AddValueChanged(i, ItemsSourceChanged);
}
}
#region Event Handlers
/// <summary>
/// Handle the GotFocus event on the control
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="RoutedEventArgs"/> that contains the event data.</param>
private static void Control_GotKeyboardFocus(object sender, RoutedEventArgs e)
{
Control c = (Control)sender;
if (ShouldShowWatermark(c))
{
RemoveWatermark(c);
}
}
/// <summary>
/// Handle the Loaded and LostFocus event on the control
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="RoutedEventArgs"/> that contains the event data.</param>
private static void Control_Loaded(object sender, RoutedEventArgs e)
{
Control control = (Control)sender;
if (ShouldShowWatermark(control))
{
ShowWatermark(control);
}
}
/// <summary>
/// Event handler for the items source changed event
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="EventArgs"/> that contains the event data.</param>
private static void ItemsSourceChanged(object sender, EventArgs e)
{
ItemsControl c = (ItemsControl)sender;
if (c.ItemsSource != null)
{
if (ShouldShowWatermark(c))
{
ShowWatermark(c);
}
else
{
RemoveWatermark(c);
}
}
else
{
ShowWatermark(c);
}
}
/// <summary>
/// Event handler for the items changed event
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="ItemsChangedEventArgs"/> that contains the event data.</param>
private static void ItemsChanged(object sender, ItemsChangedEventArgs e)
{
ItemsControl control;
if (itemsControls.TryGetValue(sender, out control))
{
if (ShouldShowWatermark(control))
{
ShowWatermark(control);
}
else
{
RemoveWatermark(control);
}
}
}
#endregion
#region Helper Methods
/// <summary>
/// Remove the watermark from the specified element
/// </summary>
/// <param name="control">Element to remove the watermark from</param>
private static void RemoveWatermark(UIElement control)
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(control);
// layer could be null if control is no longer in the visual tree
if (layer != null)
{
Adorner[] adorners = layer.GetAdorners(control);
if (adorners == null)
{
return;
}
foreach (Adorner adorner in adorners)
{
if (adorner is WatermarkAdorner)
{
adorner.Visibility = Visibility.Hidden;
layer.Remove(adorner);
}
}
}
}
/// <summary>
/// Show the watermark on the specified control
/// </summary>
/// <param name="control">Control to show the watermark on</param>
private static void ShowWatermark(Control control)
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(control);
// layer could be null if control is no longer in the visual tree
if (layer != null)
{
layer.Add(new WatermarkAdorner(control, GetWatermark(control)));
}
}
/// <summary>
/// Indicates whether or not the watermark should be shown on the specified control
/// </summary>
/// <param name="c"><see cref="Control"/> to test</param>
/// <returns>true if the watermark should be shown; false otherwise</returns>
private static bool ShouldShowWatermark(Control c)
{
if (c is ComboBox)
{
return (c as ComboBox).Text == string.Empty;
}
else if (c is TextBoxBase)
{
return (c as TextBox).Text == string.Empty;
}
else if (c is ItemsControl)
{
return (c as ItemsControl).Items.Count == 0;
}
else
{
return false;
}
}
#endregion
}
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
/// <summary>
/// Adorner for the watermark
/// </summary>
internal class WatermarkAdorner : Adorner
{
#region Private Fields
/// <summary>
/// <see cref="ContentPresenter"/> that holds the watermark
/// </summary>
private readonly ContentPresenter contentPresenter;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="WatermarkAdorner"/> class
/// </summary>
/// <param name="adornedElement"><see cref="UIElement"/> to be adorned</param>
/// <param name="watermark">The watermark</param>
public WatermarkAdorner(UIElement adornedElement, object watermark) :
base(adornedElement)
{
this.IsHitTestVisible = false;
this.contentPresenter = new ContentPresenter();
this.contentPresenter.Content = watermark;
this.contentPresenter.Opacity = 0.5;
this.contentPresenter.Margin = new Thickness(Control.Margin.Left + Control.Padding.Left, Control.Margin.Top + Control.Padding.Top, 0, 0);
if (this.Control is ItemsControl && !(this.Control is ComboBox))
{
this.contentPresenter.VerticalAlignment = VerticalAlignment.Center;
this.contentPresenter.HorizontalAlignment = HorizontalAlignment.Center;
}
// Hide the control adorner when the adorned element is hidden
Binding binding = new Binding("IsVisible");
binding.Source = adornedElement;
binding.Converter = new BooleanToVisibilityConverter();
this.SetBinding(VisibilityProperty, binding);
}
#endregion
#region Protected Properties
/// <summary>
/// Gets the number of children for the <see cref="ContainerVisual"/>.
/// </summary>
protected override int VisualChildrenCount
{
get { return 1; }
}
#endregion
#region Private Properties
/// <summary>
/// Gets the control that is being adorned
/// </summary>
private Control Control
{
get { return (Control)this.AdornedElement; }
}
#endregion
#region Protected Overrides
/// <summary>
/// Returns a specified child <see cref="Visual"/> for the parent <see cref="ContainerVisual"/>.
/// </summary>
/// <param name="index">A 32-bit signed integer that represents the index value of the child <see cref="Visual"/>. The value of index must be between 0 and <see cref="VisualChildrenCount"/> - 1.</param>
/// <returns>The child <see cref="Visual"/>.</returns>
protected override Visual GetVisualChild(int index)
{
return this.contentPresenter;
}
/// <summary>
/// Implements any custom measuring behavior for the adorner.
/// </summary>
/// <param name="constraint">A size to constrain the adorner to.</param>
/// <returns>A <see cref="Size"/> object representing the amount of layout space needed by the adorner.</returns>
protected override Size MeasureOverride(Size constraint)
{
// Here's the secret to getting the adorner to cover the whole control
this.contentPresenter.Measure(Control.RenderSize);
return Control.RenderSize;
}
/// <summary>
/// When overridden in a derived class, positions child elements and determines a size for a <see cref="FrameworkElement"/> derived class.
/// </summary>
/// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param>
/// <returns>The actual size used.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
this.contentPresenter.Arrange(new Rect(finalSize));
return finalSize;
}
#endregion
}
Sample to Use this attached Property.
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Height="403"
Width="346"
Title="Window1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<AdornerDecorator Grid.Row="0">
<TextBox VerticalAlignment="Center" >
<local:WatermarkService.Watermark>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="12">TextBox Water Mask </TextBlock>
</local:WatermarkService.Watermark>
</TextBox>
</AdornerDecorator>
<AdornerDecorator Grid.Row="1">
<ComboBox ItemsSource="{Binding Items}">
<local:WatermarkService.Watermark>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="12">Combo Box WaterMask</TextBlock>
</local:WatermarkService.Watermark>
</ComboBox>
</AdornerDecorator>
</Grid>
</Window>
For attached properties you need to use parentheses in your binding path:
<Label Content="{Binding Path=(view:SomeClass.Tag)}" />
This is written here along with explanations on how to bind to other types, such as indexers and collection views.
I'm looking for a simple time picker control for WPF.
I've found this one:
http://marlongrech.wordpress.com/2007/11/18/time-picker/
but it has some issues e.g. you can't type in "00" into it, the second zero won't appear.
Silverlight seems to have one:
http://jesseliberty.com/2009/03/28/toolkit-control-%E2%80%93-timepicker/
but it's not for WPF.
The WPF Toolkit has a DatePicker but not a TimePicker by itself. Or is there a way to allow the user to enter time and date in the WPFToolkit DatePicker? It returns a DateTime in SelectedDate but I don't see how to allow the user to also choose the time with this control.
What is the best free WPF control to allow users enter time in HH:MM:SS format?
I like the controls from the extended WPF toolkit: Link to source code on github
In the package, the DateTimeUpDown is contained which can be used for your purpose. If you don't want to use the Date part of it, you can set the Format to "ShortTime" or whatever format you wanna have.
Hope that helps.
I could't find one on the web, so I created it from scratch. I don't completely understand Dependency Properties, so i'm omitted those for now. The control is a 12 hour time picker control. I'm new to WPF so I don't fully understand all the new language syntaxes but this control will do the trick for me in the project i'm creating at home.
It supports setting time as a DateTime or a TimeSpan.
Below I will paste the XAML and the Code-Behind.
The XAML
<UserControl x:Class="WorkDayManager3.WPF.UserControls.TimeControl"
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="35" d:DesignWidth="100">
<Border BorderBrush="LightBlue" BorderThickness="1" Margin="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="5" />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox x:Name="txtHours" BorderThickness="0" MaxLength="2" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" Text="1" KeyUp="txt_KeyUp" MouseWheel="txt_MouseWheel" PreviewKeyUp="txt_PreviewKeyUp" />
<TextBlock Text=":" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBox x:Name="txtMinutes" BorderThickness="0" MaxLength="2" TextAlignment="Center" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" Text="00" KeyUp="txt_KeyUp" MouseWheel="txt_MouseWheel" PreviewKeyUp="txt_PreviewKeyUp" />
<TextBox x:Name="txtAmPm" BorderThickness="0" MaxLength="2" TextAlignment="Center" Grid.Column="3" HorizontalAlignment="Left" VerticalAlignment="Center" PreviewTextInput="txtAmPm_PreviewTextInput" Text="AM" KeyUp="txt_KeyUp" MouseWheel="txt_MouseWheel" Padding="0, 0, 3, 0" />
<Grid Grid.Column="4">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button x:Name="btnUp" Focusable="False" Click="btnUp_Click">
<TextBlock Text="p" FontFamily="Wingdings 3" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Button>
<Button x:Name="btnDown" Grid.Row="1" Focusable="False" Click="btnDown_Click">
<TextBlock Text="q" FontFamily="Wingdings 3" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Button>
</Grid>
</Grid>
</Border>
</UserControl>
The Code-Behind
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WorkDayManager3.WPF.UserControls
{
/// <summary>
/// Interaction logic for TimeControl.xaml
/// </summary>
public partial class TimeControl : UserControl
{
public TimeControl()
{
InitializeComponent();
}
#region Properties
/// <summary>
/// Gets or sets the date time value.
/// </summary>
/// <value>The date time value.</value>
public DateTime? DateTimeValue
{
get
{
string hours = this.txtHours.Text;
string minutes = this.txtMinutes.Text;
string amPm = this.txtAmPm.Text;
if (!string.IsNullOrWhiteSpace(hours)
&& !string.IsNullOrWhiteSpace(minutes)
&& !string.IsNullOrWhiteSpace(amPm))
{
string value = string.Format("{0}:{1} {2}", this.txtHours.Text, this.txtMinutes.Text, this.txtAmPm.Text);
DateTime time = DateTime.Parse(value);
return time;
}
else
{
return null;
}
}
set
{
DateTime? time = value;
if (time.HasValue)
{
string timeString = time.Value.ToShortTimeString();
//9:54 AM
string[] values = timeString.Split(':', ' ');
if (values.Length == 3)
{
this.txtHours.Text = values[0];
this.txtMinutes.Text = values[1];
this.txtAmPm.Text = values[2];
}
}
}
}
/// <summary>
/// Gets or sets the time span value.
/// </summary>
/// <value>The time span value.</value>
public TimeSpan? TimeSpanValue
{
get
{
DateTime? time = this.DateTimeValue;
if (time.HasValue)
{
return new TimeSpan(time.Value.Ticks);
}
else
{
return null;
}
}
set
{
TimeSpan? timeSpan = value;
if (timeSpan.HasValue)
{
this.DateTimeValue = new DateTime(timeSpan.Value.Ticks);
}
}
}
#endregion
#region Event Subscriptions
/// <summary>
/// Handles the Click event of the btnDown control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
private void btnDown_Click(object sender, RoutedEventArgs e)
{
string controlId = this.GetControlWithFocus().Name;
if ("txtHours".Equals(controlId))
{
this.ChangeHours(false);
}
else if ("txtMinutes".Equals(controlId))
{
this.ChangeMinutes(false);
}
else if ("txtAmPm".Equals(controlId))
{
this.ToggleAmPm();
}
}
/// <summary>
/// Handles the Click event of the btnUp control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
private void btnUp_Click(object sender, RoutedEventArgs e)
{
string controlId = this.GetControlWithFocus().Name;
if ("txtHours".Equals(controlId))
{
this.ChangeHours(true);
}
else if ("txtMinutes".Equals(controlId))
{
this.ChangeMinutes(true);
}
else if ("txtAmPm".Equals(controlId))
{
this.ToggleAmPm();
}
}
/// <summary>
/// Handles the PreviewTextInput event of the txtAmPm control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.TextCompositionEventArgs"/> instance containing the event data.</param>
private void txtAmPm_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
// prevent users to type text
e.Handled = true;
}
/// <summary>
/// Handles the KeyUp event of the txt control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> instance containing the event data.</param>
private void txt_KeyUp(object sender, KeyEventArgs e)
{
// check for up and down keyboard presses
if (Key.Up.Equals(e.Key))
{
btnUp_Click(this, null);
}
else if (Key.Down.Equals(e.Key))
{
btnDown_Click(this, null);
}
}
/// <summary>
/// Handles the MouseWheel event of the txt control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseWheelEventArgs"/> instance containing the event data.</param>
private void txt_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0)
{
btnUp_Click(this, null);
}
else
{
btnDown_Click(this, null);
}
}
/// <summary>
/// Handles the PreviewKeyUp event of the txt control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> instance containing the event data.</param>
private void txt_PreviewKeyUp(object sender, KeyEventArgs e)
{
TextBox textBox = (TextBox)sender;
// make sure all characters are number
bool allNumbers = textBox.Text.All(Char.IsNumber);
if (!allNumbers)
{
e.Handled = true;
return;
}
// make sure user did not enter values out of range
int value;
int.TryParse(textBox.Text, out value);
if ("txtHours".Equals(textBox.Name) && value > 12)
{
EnforceLimits(e, textBox);
}
else if ("txtMinutes".Equals(textBox.Name) && value > 59)
{
EnforceLimits(e, textBox);
}
}
#endregion
#region Methods
/// <summary>
/// Changes the hours.
/// </summary>
/// <param name="isUp">if set to <c>true</c> [is up].</param>
private void ChangeHours(bool isUp)
{
int value = Convert.ToInt32(this.txtHours.Text);
if (isUp)
{
value += 1;
if (value == 13)
{
value = 1;
}
}
else
{
value -= 1;
if (value == 0)
{
value = 12;
}
}
this.txtHours.Text = Convert.ToString(value);
}
/// <summary>
/// Changes the minutes.
/// </summary>
/// <param name="isUp">if set to <c>true</c> [is up].</param>
private void ChangeMinutes(bool isUp)
{
int value = Convert.ToInt32(this.txtMinutes.Text);
if (isUp)
{
value += 1;
if (value == 60)
{
value = 0;
}
}
else
{
value -= 1;
if (value == -1)
{
value = 59;
}
}
string textValue = Convert.ToString(value);
if (value < 10)
{
textValue = "0" + Convert.ToString(value);
}
this.txtMinutes.Text = textValue;
}
/// <summary>
/// Enforces the limits.
/// </summary>
/// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> instance containing the event data.</param>
/// <param name="textBox">The text box.</param>
/// <param name="enteredValue">The entered value.</param>
private static void EnforceLimits(KeyEventArgs e, TextBox textBox)
{
string enteredValue = GetEnteredValue(e.Key);
string text = textBox.Text.Replace(enteredValue, "");
if (string.IsNullOrEmpty(text))
{
text = enteredValue;
}
textBox.Text = text;
e.Handled = true;
}
/// <summary>
/// Gets the control with focus.
/// </summary>
/// <returns></returns>
private TextBox GetControlWithFocus()
{
TextBox txt = new TextBox();
if (this.txtHours.IsFocused)
{
txt = this.txtHours;
}
else if (this.txtMinutes.IsFocused)
{
txt = this.txtMinutes;
}
else if (this.txtAmPm.IsFocused)
{
txt = this.txtAmPm;
}
return txt;
}
/// <summary>
/// Gets the entered value.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
private static string GetEnteredValue(Key key)
{
string value = string.Empty;
switch (key)
{
case Key.D0:
case Key.NumPad0:
value = "0";
break;
case Key.D1:
case Key.NumPad1:
value = "1";
break;
case Key.D2:
case Key.NumPad2:
value = "2";
break;
case Key.D3:
case Key.NumPad3:
value = "3";
break;
case Key.D4:
case Key.NumPad4:
value = "4";
break;
case Key.D5:
case Key.NumPad5:
value = "5";
break;
case Key.D6:
case Key.NumPad6:
value = "6";
break;
case Key.D7:
case Key.NumPad7:
value = "7";
break;
case Key.D8:
case Key.NumPad8:
value = "8";
break;
case Key.D9:
case Key.NumPad9:
value = "9";
break;
}
return value;
}
/// <summary>
/// Toggles the am pm.
/// </summary>
private void ToggleAmPm()
{
if ("AM".Equals(this.txtAmPm.Text))
{
this.txtAmPm.Text = "PM";
}
else
{
this.txtAmPm.Text = "AM";
}
}
#endregion
}
}
That's the control, feel free to make modifications as needed. It's not perfect but it's better than other controls i've found
You can roll your own pretty easily as shown here. And that way, you can get exactly what you want.
The MahApps library has a Timepicker control since version 1.3.0.