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.
Related
What i'm trying to do is having an image that describe something with content in and there is an indicators in the image (the image is already like this)
ex: enter image description here
And my labels will cover these yellow parts
and after placing them, the should stay covering the same part regardless of the window size.
I have tried various things, starting from this article. and after many tries here is my current code, the problem in it is:
1- It should be already follow the same position but i don't find it working.
2- I don't think my approach is the right way to do this, i'm certain there should be an easier way.
First: Window1.xaml
<Window.Resources>
<!--
A data-template that defines the visuals for a rectangle.
-->
<DataTemplate
DataType="{x:Type local:RectangleViewModel}"
>
<Grid>
<Thumb
Width="20"
Height="20"
DragDelta="Thumb_DragDelta"
>
<Thumb.Template>
<ControlTemplate>
<Border
Background="Blue"
Cursor="Hand"
>
<Viewbox>
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ImageWidth ,FallbackValue=1}" />
</Viewbox>
</Border>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Grid>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<!--
Initialise the view model that supplies the UI with data.
-->
<local:ViewModel />
</Window.DataContext>
<Grid Background="Aqua">
<Image Source="pack://application:,,,/Images/1111.png" SizeChanged="Image_SizeChanged" Stretch="Uniform"/>
<!--
This ItemsControl presents the colored rectangles.
The data-template that defines the visuals for each rectangle is in the
resources section at the start of this file.
-->
<ItemsControl
ItemsSource="{Binding Rectangles}"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter
Property="Canvas.Left"
Value="{Binding X}"
/>
<Setter
Property="Canvas.Top"
Value="{Binding Y}"
/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
Second: Window1.xaml.cs
using System.Linq;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace SampleCode
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
SizeChanged += Window1_SizeChanged;
}
/// <summary>
/// Handle the resize of the window
/// </summary>
private void Window1_SizeChanged(object sender, SizeChangedEventArgs e)
{
Size oldSize = e.PreviousSize;
Size newSize = e.NewSize;
((ViewModel)this.DataContext).Rectangles.ToList().ForEach(i => i.update(newSize));
}
/// <summary>
/// Hundle the resize of the window
/// </summary>
private void Image_SizeChanged(object sender, SizeChangedEventArgs e)
{
((ViewModel)this.DataContext).Rectangles.ToList().ForEach(i => i.ImageSize = e.NewSize);
}
/// <summary>
/// Handle the user dragging the rectangle.
/// </summary>
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Thumb thumb = (Thumb)sender;
RectangleViewModel myRectangle = (RectangleViewModel)thumb.DataContext;
//
// Update the the position of the rectangle in the view-model.
//
myRectangle.X += e.HorizontalChange;
myRectangle.Y += e.VerticalChange;
}
}
}
Third: ViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace SampleCode
{
/// <summary>
/// A simple example of a view-model.
/// </summary>
public class ViewModel : INotifyPropertyChanged
{
#region Data Members
/// <summary>
/// The list of rectangles that is displayed in the ListBox.
/// </summary>
private ObservableCollection<RectangleViewModel> rectangles = new ObservableCollection<RectangleViewModel>();
/// <summary>
/// The image size
/// </summary>
private Size _imageSize ;
#endregion Data Members
public Size ImageSize
{
get => _imageSize; set
{
{
if (_imageSize == value)
{
return;
}
_imageSize = value;
OnPropertyChanged("ImageWidth");
};
}
}
public ViewModel()
{
// Populate the view model with some example data.
var r1 = new RectangleViewModel();
rectangles.Add(r1);
}
/// <summary>
/// The list of rectangles that is displayed in the ListBox.
/// </summary>
public ObservableCollection<RectangleViewModel> Rectangles
{
get
{
return rectangles;
}
}
#region INotifyPropertyChanged Members
/// <summary>
/// Raises the 'PropertyChanged' event when the value of a property of the view model has changed.
/// </summary>
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// 'PropertyChanged' event that is raised when the value of a property of the view model has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
Forth: RectangleViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Media;
using System.Windows;
namespace SampleCode
{
/// <summary>
/// Defines the view-model for a simple displayable rectangle.
/// </summary>
public class RectangleViewModel : INotifyPropertyChanged
{
#region Data Members
/// <summary>
/// The X coordinate of the location of the rectangle (in content coordinates).
/// </summary>
private double x;
/// <summary>
/// The Y coordinate of the location of the rectangle (in content coordinates).
/// </summary>
private double y;
/// <summary>
/// The size of the current window
/// </summary>
public Size mCurrentWindwoSize;
#endregion Data Members
public RectangleViewModel()
{
}
/// <summary>
/// The size of the background image
/// </summary>
public Size ImageSize { get; set; }
/// <summary>
/// The X coordinate of the location of the rectangle (in content coordinates).
/// </summary>
public double X
{
get
{
return (mCurrentWindwoSize.Width-ImageSize.Width)/2 + Data.x * ImageSize.Width;
}
set
{
if ((mCurrentWindwoSize.Width - ImageSize.Width) / 2 + Data.x * ImageSize.Width == value)return;
Data.x =( value - (mCurrentWindwoSize.Width - ImageSize.Width) / 2 )/ ImageSize.Width;
OnPropertyChanged("X");
}
}
/// <summary>
/// The Y coordinate of the location of the rectangle (in content coordinates).
/// </summary>
public double Y
{
get
{
return (mCurrentWindwoSize.Height - ImageSize.Height) / 2 + Data.y * ImageSize.Height;
}
set
{
if ((mCurrentWindwoSize.Height - ImageSize.Height) / 2 + Data.y * ImageSize.Height == value) return;
Data.y = (value - (mCurrentWindwoSize.Height - ImageSize.Height) / 2) / ImageSize.Height;
OnPropertyChanged("Y");
}
}
public void update(Size size)
{
mCurrentWindwoSize = size;
OnPropertyChanged("X");
OnPropertyChanged("Y");
}
#region INotifyPropertyChanged Members
/// <summary>
/// Raises the 'PropertyChanged' event when the value of a property of the view model has changed.
/// </summary>
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// 'PropertyChanged' event that is raised when the value of a property of the view model has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
Fifth: Data.cs
namespace SampleCode
{
public static class Data
{
public static double x = 0.5;//default
public static double y = 0.5;//default
}
}
Thanks in advance.
I finally managed to do it,
The main issue was that i forget that the windows upper panel and border are included in ActualWidth and ActualHeight, and after some fixes here is the final code:
First: Window1.xaml
<Window.Resources>
<!--
A data-template that defines the visuals for a rectangle.
-->
<DataTemplate
DataType="{x:Type local:RectangleViewModel}"
>
<Grid>
<Thumb
Width="{Binding LabelWidth}"
Height="{Binding LabelWidth}"
DragDelta="Thumb_DragDelta"
>
<Thumb.Template>
<ControlTemplate>
<Border
Background="Blue"
Cursor="Hand"
>
<Viewbox>
<TextBlock Text="1" />
</Viewbox>
</Border>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Grid>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<!--
Initialise the view model that supplies the UI with data.
-->
<local:ViewModel />
</Window.DataContext>
<Grid Background="Aqua">
<Image Source="pack://application:,,,/Images/1112.png" Stretch="Uniform"/>
<!--
This ItemsControl presents the colored rectangles.
The data-template that defines the visuals for each rectangle is in the
resources section at the start of this file.
-->
<ItemsControl
ItemsSource="{Binding Rectangles}"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas SizeChanged="Canvas_SizeChanged"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter
Property="Canvas.Left"
Value="{Binding X}"
/>
<Setter
Property="Canvas.Top"
Value="{Binding Y}"
/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
Second: Window1.xaml.cs
using System.Linq;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace SampleCode
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
/// <summary>
/// Handle the user dragging the rectangle.
/// </summary>
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Thumb thumb = (Thumb)sender;
RectangleViewModel myRectangle = (RectangleViewModel)thumb.DataContext;
//
// Update the the position of the rectangle in the view-model.
//
myRectangle.X += e.HorizontalChange;
myRectangle.Y += e.VerticalChange;
}
/// <summary>
/// Hundle the resize of the canvas
/// </summary>
private void Canvas_SizeChanged(object sender, SizeChangedEventArgs e)
{
Size newSize = e.NewSize;
((ViewModel)this.DataContext).Rectangles.ToList().ForEach(i => i.update(e.NewSize));
}
}
}
Third: ViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace SampleCode
{
/// <summary>
/// A simple example of a view-model.
/// </summary>
public class ViewModel : INotifyPropertyChanged
{
#region Data Members
/// <summary>
/// The list of rectangles that is displayed in the ListBox.
/// </summary>
private ObservableCollection<RectangleViewModel> rectangles = new ObservableCollection<RectangleViewModel>();
#endregion
public ViewModel()
{
// Populate the view model with some example data.
var r1 = new RectangleViewModel(0.1,0.3);
rectangles.Add(r1);
var r2 = new RectangleViewModel(0.2,0.4);
rectangles.Add(r2);
}
/// <summary>
/// The list of rectangles that is displayed in the ListBox.
/// </summary>
public ObservableCollection<RectangleViewModel> Rectangles
{
get
{
return rectangles;
}
}
#region INotifyPropertyChanged Members
/// <summary>
/// Raises the 'PropertyChanged' event when the value of a property of the view model has changed.
/// </summary>
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// 'PropertyChanged' event that is raised when the value of a property of the view model has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
and Finally, Forth: RectabgleViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Media;
using System.Windows;
namespace SampleCode
{
/// <summary>
/// Defines the view-model for a simple displayable rectangle.
/// </summary>
public class RectangleViewModel : INotifyPropertyChanged
{
#region Data Members
/// <summary>
/// The aspect ration of the image
/// </summary>
private double ratio = 1.375;
/// <summary>
/// The label width
/// </summary>
private double mLabelWidth = 20;
/// <summary>
/// The X coordinate of the location of the rectangle (in content coordinates).
/// </summary>
private double mX;
/// <summary>
/// The Y coordinate of the location of the rectangle (in content coordinates).
/// </summary>
private double mY;
/// <summary>
/// The size of the current window
/// </summary>
public Size mCurrentWindwoSize = new Size(450,300);
/// <summary>
/// The size of the label as a percantage from the image size
/// </summary>
private readonly double sizePercentage = 0.05;
#endregion Data Members
public RectangleViewModel(double x, double y)
{
mX = x;
mY = y;
}
/// <summary>
/// The size of the background image
/// </summary>
public Size ImageSize { get; set; }
/// <summary>
/// The width of the label
/// </summary>
public double LabelWidth
{
get { return mLabelWidth; }
set { if (value == mLabelWidth)
return;
else
mLabelWidth = value;
OnPropertyChanged("LabelWidth");
}
}
/// <summary>
/// The X coordinate of the location of the rectangle (in content coordinates).
/// </summary>
public double X
{
get
{
return (mCurrentWindwoSize.Width-ImageSize.Width)/2 + mX * ImageSize.Width - mLabelWidth/2;
}
set
{
//if ((mCurrentWindwoSize.Width - ImageSize.Width) / 2 + mX * ImageSize.Width == value)return;
mX =( value - (mCurrentWindwoSize.Width - ImageSize.Width) / 2 + mLabelWidth/2)/ ImageSize.Width;
OnPropertyChanged("X");
}
}
/// <summary>
/// The Y coordinate of the location of the rectangle (in content coordinates).
/// </summary>
public double Y
{
get
{
return (mCurrentWindwoSize.Height - ImageSize.Height) / 2 + mY * ImageSize.Height - mLabelWidth/2 ;
}
set
{
//if ((mCurrentWindwoSize.Height - ImageSize.Height) / 2 + mY * ImageSize.Height == value) return;
mY = (value - (mCurrentWindwoSize.Height - ImageSize.Height) / 2 + mLabelWidth/2) / ImageSize.Height;
OnPropertyChanged("Y");
}
}
public void update(Size windowSize)
{
mCurrentWindwoSize = windowSize;
if (windowSize.Height > windowSize.Width * ratio)
{
ImageSize = new Size(windowSize.Width, windowSize.Width * ratio);
}
else
{
ImageSize = new Size(windowSize.Height / ratio, windowSize.Height);
}
LabelWidth = ImageSize.Width * sizePercentage;
X = (mCurrentWindwoSize.Width - ImageSize.Width) / 2 + mX * ImageSize.Width - mLabelWidth/2;
Y = (mCurrentWindwoSize.Height - ImageSize.Height) / 2 + mY * ImageSize.Height - mLabelWidth/2;
}
#region INotifyPropertyChanged Members
/// <summary>
/// Raises the 'PropertyChanged' event when the value of a property of the view model has changed.
/// </summary>
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// 'PropertyChanged' event that is raised when the value of a property of the view model has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
I think that it still needs some clean up but at least it do the job required by it.
I will try to make it cleaner while moving it to the actual project.
Thanks.
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 am learning WPF with MVVM Design pattern and trying to understand how to get some things done outside of the code behind.
I have a login page, pictured below.
I have a password control I took from http://www.wpftutorial.net/PasswordBox.html.
I would for now for the case of simple understanding, like to ask you if my code is in the right class/set correctly to abide to MVVVM regulations with seperation of concerns.
I currently have an if statement to check if the details match a name string and a password string.
The code is in the code behind. I just wonder if this is correct with regards to MVVM. I wonder how you implement this in a ViewModel?
private void OK_Click(object sender, RoutedEventArgs e)
{
if (emp.Name == "ep" && emp.Password == "pass")
{
MessageBox.Show("namd and Pw accepted");
//open new page
var HomeScreen = new HomeScreen();
HomeScreen.Show();
}
else
{
//deny access
MessageBox.Show("Incorrect username and password");
}
}
Instead of implementing the button click handler in the code behind, use an ICommand and bind it to the button's event in XAML.
Here is a really great tutorial that got me starting in MVVM:
WPF Apps With The Model-View-ViewModel Design Pattern
[Edited to add sample code]
Heres a simple code example to do just what your example does, but in MVVM style and without any code-behind code at all.
1) Create a new WPF Solution, for this small example I named it simply "WpfApplication".
2) Edit the code of the automatically created MainWindow.xaml:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:WpfApplication"
Title="MainWindow" Height="234" Width="282">
<!-- Create the ViewModel as the initial DataContext -->
<Window.DataContext>
<viewModel:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="70,31,0,0"
Name="textBox1"
VerticalAlignment="Top"
Width="120"
Text="{Binding Path=Name}"/>
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="70,72,0,0"
Name="textBox2"
VerticalAlignment="Top"
Width="120"
Text="{Binding Path=Password}" />
<Label Content="Name"
Height="28"
HorizontalAlignment="Left"
Margin="22,29,0,0"
Name="label1"
VerticalAlignment="Top" />
<Label Content="PW"
Height="28"
HorizontalAlignment="Left"
Margin="22,70,0,0"
Name="label2"
VerticalAlignment="Top" />
<Button Content="OK"
Height="23"
HorizontalAlignment="Left"
Margin="70,119,0,0"
Name="button1"
VerticalAlignment="Top"
Width="120"
Command="{Binding Path=LoginCommand}"
CommandParameter="{Binding Path=.}"
/>
</Grid>
</Window>
(Ignore the Width, Height, Margin values, these are just copied & pasted from my designer and were quick & dirty adjusted to roughly look like your screenshot ;-) )
3) Create the Command class that will handle your log in logic. Note that I did not implement it as a RelayCommand like in Josh Smith's tutorial but it would be easy to modify the code accordingly:
namespace WpfApplication
{
using System;
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Checks the user credentials.
/// </summary>
public class LoginCommand : ICommand
{
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public void Execute(object parameter)
{
MainWindowViewModel viewModel = parameter as MainWindowViewModel;
if (viewModel == null)
{
return;
}
if (viewModel.Name == "ep" && viewModel.Password == "pass")
{
MessageBox.Show("namd and Pw accepted");
//open new page
var HomeScreen = new HomeScreen();
HomeScreen.Show();
}
else
{
//deny access
MessageBox.Show("Incorrect username and password");
}
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public bool CanExecute(object parameter)
{
// Update this for your application's needs.
return true;
}
public event EventHandler CanExecuteChanged;
}
}
4) Now add the ViewModel that will communicate with the View and provide it the command interface and values:
namespace WpfApplication
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Input;
/// <summary>
/// TODO: Update summary.
/// </summary>
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Signal that the property value with the specified name has changed.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion Implementation of INotifyPropertyChanged
#region Backing Fields
/// <summary>
/// Gets or sets the value of Name.
/// </summary>
private string name;
/// <summary>
/// Gets or sets the value of Password.
/// </summary>
private string password;
/// <summary>
/// Gets or sets the value of LoginCommand.
/// </summary>
private LoginCommand loginCommand;
#endregion Backing Fields
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
/// </summary>
public MainWindowViewModel()
{
this.loginCommand = new LoginCommand();
}
#endregion Constructor
#region Properties
/// <summary>
/// Gets or sets the name of the user.
/// </summary>
public string Name
{
get
{
return this.name;
}
set
{
if (this.name == value)
{
return;
}
this.name = value;
this.OnPropertyChanged("Name");
}
}
/// <summary>
/// Gets or sets the user password.
/// </summary>
public string Password
{
get
{
return this.password;
}
set
{
if (this.password == value)
{
return;
}
this.password = value;
this.OnPropertyChanged("Password");
}
}
/// <summary>
/// Gets or sets the command object that handles the login.
/// </summary>
public ICommand LoginCommand
{
get
{
return this.loginCommand;
}
set
{
if (this.loginCommand == value)
{
return;
}
this.loginCommand = (LoginCommand)value;
this.OnPropertyChanged("LoginCommand");
}
}
#endregion Properties
}
}
5) Finally, do not forget to add an additional Window HomeScreen that will be opened by the LoginCommand to the solution. :-)
To implement that, you should bind the button's command like this -
<Button Content ="OK" Command = {Binding OKCommand} />
In your ViewModel, create an ICommand property for this binding like this -
public class MyViewModel() : INotifyPropertyChanged
{
ICommand _OKCommand;
public ICommand OKCommad
{
get { return _OKCommand; }
set { _OKCommand = value; PropertyChanged(OKCommad); }
}
public MyViewModel()
{
this.OKCommand += new DelegateCommand(OKCommand_Execute);
}
public void OKCommand_Execute()
{
// Code for button click here
}
}
Also note that for using this delegate command, you need to add reference to Microsoft.Practices.Prism.dll
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.