I've created a UserControl
that should act as a control inside a Window
:
XAML:
<UserControl x:Class="MyApplication.UserControls.DateTimePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:MyApplication.Converters"
x:Name="dtp">
<UserControl.Resources>
<converters:NullDateTimeConverter x:Key="nullDateTimeConverter" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="AUTO" />
</Grid.ColumnDefinitions>
<TextBox HorizontalAlignment="Stretch"
IsReadOnly="True"
Text="{Binding Path=SelectedDateTime,
ElementName=dtp,
Converter={StaticResource nullDateTimeConverter},
StringFormat={}{0:MM/dd/yyyy HH:mm},
UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Row="0"
Grid.Column="1"
Width="50"
BorderBrush="Orange"
BorderThickness="1"
Command="{Binding ChangePopupStatusCommand}"
Style="{DynamicResource SquareButtonStyle}">
<Rectangle Width="20"
Height="20"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button},
Path=Foreground}">
<Rectangle.OpacityMask>
<VisualBrush Stretch="Fill" Visual="{StaticResource appbar_calendar}" />
</Rectangle.OpacityMask>
</Rectangle>
</Button>
<Popup Grid.Row="1"
Grid.Column="0"
Width="400"
IsOpen="{Binding IsPopupOpen}"
PopupAnimation="Slide">
<DockPanel LastChildFill="True">
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<RepeatButton Command="{Binding ChangeTimeCommand}"
CommandParameter="addHour"
Content="+" />
<RepeatButton Grid.Column="1"
Command="{Binding ChangeTimeCommand}"
CommandParameter="addMinute"
Content="+" />
<TextBox
Grid.Row="1"
Grid.ColumnSpan="2"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center">
<TextBox.Text>
<MultiBinding StringFormat=" {0:D2}:{1:D2}">
<Binding Mode="OneWay" Path="SelectedDateTime.Hour" />
<Binding Mode="OneWay" Path="SelectedDateTime.Minute" />
</MultiBinding>
</TextBox.Text>
</TextBox>
<RepeatButton Grid.Row="2"
Command="{Binding ChangeTimeCommand}"
CommandParameter="subHour"
Content="-" />
<!-- Click="SubHours_Click" -->
<RepeatButton Grid.Row="2"
Grid.Column="1"
Command="{Binding ChangeTimeCommand}"
CommandParameter="subMinute"
Content="-" />
</Grid>
<Calendar DockPanel.Dock="Bottom"
SelectedDate="{Binding SelectedDateTime,
ElementName=dtp,
Converter={StaticResource nullDateTimeConverter}}"
Style="{StaticResource styleCalendar}" />
</DockPanel>
</Popup>
</Grid>
</UserControl>
XAML.CS
public partial class DateTimePicker : UserControl, INotifyPropertyChanged
{
public DateTimePicker()
{
InitializeComponent();
(Content as FrameworkElement).DataContext = this;
}
#region SelectedTime
public DateTime? SelectedDateTime
{
get
{
return (DateTime?)GetValue(SelectedDateTimeProperty);
}
set
{
SetValue(SelectedDateTimeProperty, value);
}
}
public static readonly DependencyProperty
SelectedDateTimeProperty =
DependencyProperty.Register("SelectedDateTime",
typeof(DateTime?),
typeof(DateTimePicker));
#endregion
private bool _isPopupOpen;
public bool IsPopupOpen
{
get { return _isPopupOpen; }
set
{
SetProperty(ref _isPopupOpen, value);
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void SetProperty<T>(ref T member, T val,
[CallerMemberName]string propertyName = null)
{
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged
#region ChangePopup Command
private CommandBase _changePopupStatusCommand;
public CommandBase ChangePopupStatusCommand
{
get { return _changePopupStatusCommand ?? (_changePopupStatusCommand = new CommandBase(ChangePopupStatus)); }
}
private void ChangePopupStatus(object obj)
{
IsPopupOpen = !IsPopupOpen;
}
#endregion
#region ChangeTime Command
private CommandBase _chageTimeCommand;
public CommandBase ChangeTimeCommand
{
get { return _chageTimeCommand ?? (_chageTimeCommand = new CommandBase(ChangeTime)); }
}
private void ChangeTime(object obj)
{
if (!SelectedDateTime.HasValue)
SelectedDateTime = DateTime.Now;
switch (obj.ToString())
{
case "addHour":
SelectedDateTime = SelectedDateTime.Value.AddHours(1);
break;
case "addMinute":
SelectedDateTime = SelectedDateTime.Value.AddMinutes(1);
break;
case "subHour":
SelectedDateTime = SelectedDateTime.Value.AddHours(-1);
break;
case "subMinute":
SelectedDateTime = SelectedDateTime.Value.AddMinutes(-1);
break;
}
}
#endregion
}
NullDateTimeConverter
:
public class NullDateTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var val = (Nullable<DateTime>)value;
if (val.HasValue && val.Value > DateTime.MinValue)
return val;
return String.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string strValue = value.ToString();
DateTime resultDateTime;
return DateTime.TryParse(strValue, out resultDateTime) ? resultDateTime : value;
}
}
Usage:
<DockPanel>
<TextBlock Text="Calibration Due" />
<uc:DateTimePicker SelectedDateTime="{Binding CalibarationDue,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
</DockPanel>
The thing I'm most unsure of is the way I use the binding
in side my picker. Any comments or suggestions?
-
\$\begingroup\$ I would suggest building a custom control. user control's though can accomplish the same result's are conventionally used to group existing elements with no logic. further more using a custom control will enable you to switch the control template with something else. and refer to your control with TemplateBinding from within it's template. \$\endgroup\$eran otzap– eran otzap2015年08月16日 04:52:12 +00:00Commented Aug 16, 2015 at 4:52
1 Answer 1
You have used two types of bindings:
1. Binding between your control and the usage DataContext
This binding is fine and is the way how WPF binding system was supposed to work with respect to the MVVM pattern.
2. Bindings between xaml controls and the xaml class (Same class which is the parent of those controls)
The interactions between xaml controls and xaml code-behind are not supposed to flow through the same MVVM pattern. This is why in the partial class you have the possibility to access directly your controls.
Example from xaml:
<Button Grid.Row="0"
Grid.Column="1"
Width="50"
BorderBrush="Orange"
BorderThickness="1"
Name="ButtonChangeStatus"
Click="ButtonChangeStatus_OnClick"
Style="{DynamicResource SquareButtonStyle}">
Example from code-behind:
void ButtonChangeStatus_OnClick(object sender, RoutedEventArgs e)
{
IsPopupOpen = !IsPopupOpen;
ButtonChangeStatus.Background = Brushes.Green;
}
Another drawback could be the usage of some properties from outside of your implementation. Suppose there is some logic which needs to open the popup without a click on the ButtonChangeStatus
button but from a click which originates outside of your implementation. To achieve such behavior the user of your control would need to bind IsOpen
to his DataContext
. But in the current implementation it's not possible without a great confusion (you need to have two IsOpen
dependency properties); it will be possible by exposing the IsOpen
dependency property to the outside ViewModel in the same way as you already did with SelectedDateTime
. Having both a local IsOpen
property and a dependency one will allow you to handle the Popup
using two sources.