I have a Window class, where I have several TextBlock elements which should receive a Background color by a value of a Binding property. The first "Converter binding" works fine and does everything expected. Today I tried to implement another "Converter binding" with another Converter used for it, but it does not work:
(I left out the ConvertBack methods because they are not necessary here):
namespace InsightTool.Gui.Helper {
[ValueConversion(typeof(double), typeof(Brush))]
public class AverageExecutionTimeToColorConverter : IValueConverter {
public object Convert (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
double val;
double.TryParse(value.ToString(), out val);
if (val >= 10000) {
return Brushes.Red;
} else if (val >= 5000) {
return Brushes.Orange;
} else {
return Brushes.Green;
}
}
}
[ValueConversion(typeof(int), typeof(Brush))]
public class ThreadsAvailableCountToColorConverter : IValueConverter {
public object Convert (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
int val;
int.TryParse(value.ToString(), out val);
if (val < 100) {
return Brushes.Red;
} else if (val < 200) {
return Brushes.Orange;
} else if (val < 500) {
return Brushes.Yellow;
} else {
return Brushes.Green;
}
}
}
}
In the Window class I used both converts as following:
<Window ...
x:Name="Main"
xmlns:Base="clr-namespace:InsightTool.Gui.Helper">
<Window.Resources>
<Base:ThreadsAvailableCountToColorConverter x:Key="ThreadsAvailableCntConverter"/>
<Base:AverageExecutionTimeToColorConverter x:Key="AvgExecutionTimeConverter"/>
</Window.Resources>
<!-- This one works fine-->
<TextBlock Width="10" Height="10" VerticalAlignment="Center" Background="{Binding ExecutionTimeAverage, Converter={StaticResource AvgExecutionTimeConverter}, ElementName=UCExecutionTimes}"/>
<!-- This one does not work-->
<TextBlock Width="10" Height="10" VerticalAlignment="Center" Background="{Binding ThreadsAvailableCount, Converter={StaticResource ThreadsAvailableCntConverter}, ElementName=Main}"/>
</Window>
Declaration of DependencyProperties:
public partial class UserControlExecutionTimes : UserControl {
public static readonly DependencyProperty ExecutionTimeAverageProperty =
DependencyProperty.Register("ExecutionTimeAverage", typeof(double), typeof(MainWindow), new FrameworkPropertyMetadata(double));
public double ExecutionTimeAverage {
get { return (double)GetValue(ExecutionTimeAverageProperty); }
set { SetValue(ExecutionTimeAverageProperty, value); }
}
}
public partial class MainWindow : Window {
public static readonly DependencyProperty ThreadsAvailableCountProperty = DependencyProperty.Register("ThreadsAvailableCount", typeof(int),
typeof(MainWindow), new FrameworkPropertyMetadata(int));
public int ThreadsAvailableCount {
get { return (int)GetValue(ThreadsAvailableCountProperty); }
set { SetValue(ThreadsAvailableCountProperty, value); }
}
}
Both DependencyProperties are set correctly and their values are displayed in the GUI. What do I miss here?
EDIT:
I also tested the following:
<Window>
<!-- This one works fine-->
<TextBlock Width="10" Height="10" VerticalAlignment="Center" Background="{Binding ThreadsAvailableCount, Converter={StaticResource AvgExecutionTimeConverter}, ElementName=Main}"/>
<!-- This one does not work-->
<TextBlock Width="10" Height="10" VerticalAlignment="Center" Background="{Binding ThreadsAvailableCount, Converter={StaticResource ThreadsAvailableCntConverter}, ElementName=Main}"/>
</Window>
It seems that there is a problem for the Binding to consume the return value of the "new" converter, but I have no clue why.
EDIT2
I check the bindings with Snoop and the result was the following:
The background property of the working converter binding looks like this:
But the background property of the not working converter binding looks this:
Another proof that ThreadsAvailableCount is set correctly (Binding to a Textblock):
It more and more seems to be a mistake in displaying the return value of the ThreadsAvailableCountToColorConverter. That is because in Debug mode, it stops at a breakpoint in the Convert method of the ThreadsAvailableCountToColorConverter. It even reachesreturn in the Convert method successfully.
Ah! Finally solved this. I had the exact same problem. With a TextBlock, with an IValueConverter converting to a Brush.
The binding was working, no errors or output. The value was getting into the IValueConverter code, I could debug right through to the return statement then... nothing!
You've done everything right, but you've automatically imported the wrong Brushes. I do it all the time with WPF.
replace the using statement:
using System.Drawing
with:
using System.Windows.Media
WPF uses System.Windows.Media.Brushes, but it's very easy to import the almost identical System.Drawing.Brushes and not notice. It all looks fine, until WPF gets hold of it and can't actually use it. But it fails 'gracefully' by falling back on the default colour.
I think could be multiple issues look at the 'output window' for binding expression errors.
1) Ensure that the textbox are rendered in separate areas and are not
overlapping.
2) Use relative path to get to the control and use it property in the binding expression
Your convertor looks fine.
Following is my xaml
<Window x:Class="StackOverflowBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:stackOverflowBinding="clr-namespace:StackOverflowBinding"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<stackOverflowBinding:ThreadsAvailableCountToColorConverter x:Key="ThreadsAvailableCntConverter"/>
<stackOverflowBinding:AverageExecutionTimeToColorConverter x:Key="AvgExecutionTimeConverter"/>
</Window.Resources>
<Grid>
<!--<DatePicker
x:Name="newtally"
Text="{Binding CustomerLastTally,Mode=TwoWay}"
Margin="0 0 0 0"
/>-->
<!-- This one works fine-->
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Width="30" Height="30" Text="Break"/>
<TextBlock Grid.Row="1" Grid.Column="0" Width="30" Height="30" VerticalAlignment="Center" Text="Break" Background="{Binding ExecutionTimeAverage, Converter={StaticResource AvgExecutionTimeConverter}, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
<!-- This one does not work-->
<TextBlock Grid.Row="2" Grid.Column="0" Width="30" Height="30" VerticalAlignment="Center" Text="Break" Background ="{Binding ThreadsAvailableCount, Converter={StaticResource ThreadsAvailableCntConverter}, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</Grid>
</Window>
Following is my code behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace StackOverflowBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
// Dependency Property
public static readonly DependencyProperty ExecutionTimeAverageProperty =
DependencyProperty.Register("ExecutionTimeAverage", typeof(DateTime),
typeof(MainWindow), new FrameworkPropertyMetadata(DateTime.Now));
// .NET Property wrapper
public DateTime ExecutionTimeAverage
{
get { return (DateTime)GetValue(ExecutionTimeAverageProperty); }
set { SetValue(ExecutionTimeAverageProperty, value); }
}
// Dependency Property
public static readonly DependencyProperty ThreadsAvailableCountProperty =
DependencyProperty.Register("ThreadsAvailableCount", typeof(int),
typeof(MainWindow), new FrameworkPropertyMetadata(40));
// .NET Property wrapper
public int ThreadsAvailableCount
{
get { return (int)GetValue(ThreadsAvailableCountProperty); }
set { SetValue(ThreadsAvailableCountProperty, value); }
}
}
}
Following is my convertor
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Media;
namespace StackOverflowBinding
{
[ValueConversion(typeof(double), typeof(Brush))]
public class AverageExecutionTimeToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double val;
double.TryParse(value.ToString(), out val);
if (val >= 10000)
{
return Brushes.Red;
}
else if (val >= 5000)
{
return Brushes.Orange;
}
else
{
return Brushes.Green;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
[ValueConversion(typeof(int), typeof(Brush))]
public class ThreadsAvailableCountToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int val;
int.TryParse(value.ToString(), out val);
if (val < 100)
{
return Brushes.Red;
}
else if (val < 200)
{
return Brushes.Orange;
}
else if (val < 500)
{
return Brushes.Yellow;
}
else
{
return Brushes.Green;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Related
I have a list box that contains a label and a text box that the user can alter. The list box contents are defined in a data template (inside window.resources). I would like to add a border to each item in the list that has been changed using a booltovisibility converter.
I think I'm having trouble because I'm trying to set the converter inside window.resources.
Can somebody please point me in the right direction?
View Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace MaintainPersonData
{
public class MaintainPersonViewModel
{
public MaintainPersonViewModel(ObservableCollection<PersonViewModel> personList)
{
}
public INotifyUser Notifier;
private ObservableCollection<PersonViewModel> _personList;
public ObservableCollection<PersonViewModel> PersonList
{
get
{
return _personList;
}
set
{
_personList = value;
OnPropertyChanged("PersonList");
}
}
private bool _changesMade;
public bool ChangesMade
{
get
{
return _changesMade;
}
set
{
_changesMade = value;
OnPropertyChanged("ChangesMade");
}
}
private bool _hasErrors;
public bool HasErrors
{
get { return _hasErrors; }
set
{
_hasErrors = value;
if (!_hasErrors)
{
ErrorMessage = "";
}
OnPropertyChanged("HasErrors");
}
}
Xaml:
<Window.Resources>
<conv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<DataTemplate x:Key="ListBoxItemTemplate">
<Border BorderBrush="LightGreen" BorderThickness="2" Visibility="{Binding ChangesMade, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid>
<TextBox x:Name="PersonTextBox" Text="{Binding PersonName, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox x:Name="PersonListBox" SelectionMode="Single" KeyboardNavigation.TabNavigation="Continue" ItemTemplate="{StaticResource ListBoxItemTemplate}" ItemsSource="{Binding PersonList}">
<!-- Code to highlight selected item (http://stackoverflow.com/questions/15366806/wpf-setting-isselected-for-listbox-when-textbox-has-focus-without-losing-selec) -->
</ListBox>
<!-- BoolToVisibilityConverter works perfectly here -->
<Label Name="ErrorLabel" Grid.Column="0" Foreground="Red" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{Binding HasErrors, Converter={StaticResource BoolToVisibilityConverter}}" >
<TextBlock Text="{Binding ErrorMessage, UpdateSourceTrigger=PropertyChanged}" />
</Label>
</Grid>
</Window>
And Finally, the converter:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace MaintainRegexData
{
class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
ChangesMade should be defined per each PersonViewMode It's because right now you will (or will not) add border to all items. Have you checked that Convert method from BoolToVisibilityConverter is invoked? And last thing - there is nothing wrong with setting converter in window resources.
first of all you Bind your ItemsSource to PersonList which it's Type is ObservableCollection<PersonViewModel> where ChangesMade include in MaintainPersonViewModel so you need to place your ChangesMade inside PersonViewModel class and make changes while personName Property changed.
and don't forget what #Frank said about Border.
I want to do Binding to specific property, and make the checkbox converter according to the property values in class.
I have an error.
This is my class:
namespace WpfApplication2
{
class Point
{
public int point { get; set; }
public Point(int x)
{
this.point = x;
}
}
}
This is my Converter:
namespace WpfApplication2
{
public class NumberToCheckedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((int)parameter >= 5)
return true;
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
}
This is my CS window's code:
namespace WpfApplication2
public partial class MainWindow : Window
{
List<Point> points;
public MainWindow()
{
InitializeComponent();
points = new List<Point>();
Random rnd = new Random();
for (int i = 0; i < 10; i++)
{
points.Add(new Point(rnd.Next()));
}
this.DataContext = points;
}
}
}
And this is the xaml:
Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:NumberToCheckedConverter x:Key="NumberToCheckedConverter"></local:NumberToCheckedConverter>
<DataTemplate x:Key="MyDataTemplate"
DataType="local:MyData">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="70" />
</Grid.ColumnDefinitions>
<TextBox Text="Over 5" />
<CheckBox Grid.Column="1" IsChecked="{Binding point, Converter={StaticResource NumberToCheckedConverter}, ConverterParameter=point}" IsEnabled="False" />
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource MyDataTemplate}" ItemsSource="{Binding}" Height="172" HorizontalAlignment="Left" Margin="0,51,-0.2,0" Name="listBox1" VerticalAlignment="Top" Width="517" >
</ListBox>
</Grid>
I have an error with the converter. What's wrong here?
A ConverterParameter is not a binding, so writing:
IsChecked="{Binding point, Converter={StaticResource NumberToCheckedConverter}, ConverterParameter=point}"
Is setting parameter to "point"; not really what you want. As it turns out, Converter Parameters aren't even Dependency Properties, and so cannot be bound.
However, you don't even need the parameter; just change your code to:
public class NumberToCheckedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((int)value >= 5)
return true;
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing; //Null would cause an error on a set back.
}
}
Converting the value will do what you want. If you wanted the threshold to be configurable, that is where ConverterParamater would come into play.
I am binding to an ObservableCollection called ScaleFactor to a ComboBox. The value of the ObservableCollection are simply 1, 2, 4 and 8. I want to use an IValueConverter to change these values to x1, x2, x4 and x8.
My MainWindow.xaml
<Window x:Class="TimeLineCanvas.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:timeline="clr-namespace:TimeLineCanvas.UserControls"
xmlns:helper="clr-namespace:TimeLineCanvas.Helpers"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<helper:ZoomConverter x:Key="ZoomConverter" />
</Grid.Resources>
<StackPanel>
<ComboBox ItemsSource="{Binding SSS}" HorizontalAlignment="Left" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding, Converter={StaticResource ZoomConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
</Window>
And the code behind
using System;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace TimeLineCanvas
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region Constructors
public MainWindow()
{
InitializeComponent();
SSS = new ObservableCollection<int>();
SSS.Add(1);
SSS.Add(2);
this.DataContext = this;
}
#endregion
public ObservableCollection<int> SSS { get; set; }
}
}
And the converter
using System;
using System.Windows.Data;
namespace TimeLineCanvas.Helpers
{
public class ZoomConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "x" + value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I don't know why this is, I'm not using MarkupExtensions so I don't think this link helps. Can any one shed any light?
Do not use a comma after Binding. This way you call the empty constructor on the Binding object.
{Binding, Converter={StaticResource ZoomConverter}}
should be
{Binding Converter={StaticResource ZoomConverter}}
I'm creating a simple WP8, but I'm having troubles hiding (changing the Visibility property) on a control.
In XAML I've added xmlns:local="clr-namespace:MyProjectName" (I've also tried with using).
The XAML is then structured as follows:
<phone:PhoneApplicationPage
x:Class="MyProjectName.Pages.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyProjectName"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d"
shell:SystemTray.IsVisible="True" Margin="0,4,0,-4" Background="#FFBD3F3F">
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneBackgroundBrush}" >
<Grid x:Name="ContentPanel" Grid.Row="1" >
<Grid.Resources>
<local:VisibilityFormatter x:Key="FormatConverter" />
</Grid.Resources>
<phone:LongListSelector Grid.Row="4">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Obj}"
Visibility="{Binding ObjVisibility,
Mode=OneWay,
Converter={StaticResource FormatConverter}}" />
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
</Grid>
The problem is at the <local:...> line: The name "VisibilityFormatter" does not exist in the namespace "clr-namespace:MyProjectName".
The class is defined as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace MyProjectName
{
public class Formatter
{
public class VisibilityFormatter : IValueConverter
{
// Retrieve the format string and use it to format the value.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var visibility = parameter as bool?;
return visibility.HasValue && visibility.Value ? Visibility.Visible : Visibility.Collapsed;
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
}
The class ObjInfo is a simple public class with two properties:
namespace MyProjectName.Models
{
public class ObjInfo
{
public bool ObjVisibility { get; set; }
public string Obj { get; set; }
}
}
It's similar to this question, but no migrating is involved. I'm developing on WP8 from the get-go.
What am I trying to achieve? Well. I'm storing whether the control should be visible or not in that bool property. Since the XAML control's property only grokks the Visibility enum, but not bool, I need to convert it to that enum.
The VisibilityFormatter is an inner class of the Formatter class. You don't really need the Formatter class, just make the VisibilityFormatter a top class, and the XAML parser will find it.
Also, the general naming convention for converters is XXXConverter and not XXXFormatter, but that's no rule.
You don't have MyProjectName.VisibilityFormatter in your project, you have
MyProjectName.Formatter.VisibilityFormatter
You should remove Formatter class and leave only:
namespace MyProjectName
{
public class VisibilityFormatter : IValueConverter
{
// Retrieve the format string and use it to format the value.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var visibility = parameter as bool?;
return visibility.HasValue && visibility.Value ? Visibility.Visible : Visibility.Collapsed;
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This is a simple datetime control that has the added feature of minutes and hours.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace foo.WizardElements
{
/// <summary>
/// Interaction logic for DateTimeRangeElement.xaml
/// </summary>
public partial class DateTimeRangeElement : UserControl
{
public DateTimeRangeElement()
{
InitializeComponent();
dp.DataContext = this;
}
private void Clear_Click(object sender, RoutedEventArgs e)
{
Date = null;
Hours = 0;
Minutes = 0;
}
public static readonly DependencyProperty DateProperty = DependencyProperty.Register("Date",
typeof(DateTime?),
typeof(DateTimeRangeElement));
public DateTime? Date
{
get { return (DateTime?)GetValue(DateProperty); }
set
{
SetValue(DateProperty, value);
}
}
public static readonly DependencyProperty HoursProperty = DependencyProperty.Register("Hours",
typeof(int),
typeof(DateTimeRangeElement));
public int Hours
{
get { return (int)GetValue(HoursProperty); }
set
{
SetValue(HoursProperty, value);
}
}
public static readonly DependencyProperty MinutesProperty = DependencyProperty.Register("Minutes",
typeof(int),
typeof(DateTimeRangeElement));
public int Minutes
{
get { return (int)GetValue(MinutesProperty); }
set
{
SetValue(MinutesProperty, value);
}
}
private void TimeUpdated()
{
if(Hours > 23)
Hours = 23;
if(Minutes > 59)
Minutes = 59;
if (Date.HasValue && (Date.Value.Hour != Hours || Date.Value.Minute != Minutes))
{
Date = new DateTime(Date.Value.Year, Date.Value.Month, Date.Value.Day, Hours, Minutes, 0);
}
if ((!Date.HasValue) && (Hours > 0 || Minutes > 0))
{
var now = DateTime.Now;
Date = new DateTime(now.Year, now.Month, now.Day, Hours, Minutes, 0);
}
}
private void Changed(object sender, object e)
{
TimeUpdated();
}
}
}
and the xaml
<UserControl x:Class="foo.WizardElements.DateTimeRangeElement"
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"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behavior="clr-namespace:foo.Behaviours"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="myDateTimeControl">
<Grid>
<StackPanel Orientation="Horizontal" Margin="2" Height="24">
<DatePicker x:Name="dp"
VerticalAlignment="top"
Foreground="LightGray"
SelectedDate="{Binding ElementName=myDateTimeControl,Path=Date, Mode=TwoWay}"
BorderBrush="{x:Null}" SelectedDateChanged="Changed"
>
<DatePicker.Background>
<SolidColorBrush Color="white" Opacity="0.2"/>
</DatePicker.Background>
</DatePicker>
<TextBox Width="30" Text="{Binding ElementName=myDateTimeControl, Path=Hours, Mode=TwoWay, StringFormat=00}" TextChanged="Changed">
<i:Interaction.Behaviors>
<behavior:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9]*$" MaxLength="3" />
</i:Interaction.Behaviors>
</TextBox>
<Label Content=":"/>
<TextBox Width="30" Text="{Binding ElementName=myDateTimeControl, Path=Minutes, Mode=TwoWay, StringFormat=00}" TextChanged="Changed">
<i:Interaction.Behaviors>
<behavior:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9]*$" MaxLength="3" />
</i:Interaction.Behaviors>
</TextBox>
<Grid HorizontalAlignment="Left" VerticalAlignment="top">
<Button
Background="Transparent"
Click="Clear_Click"
BorderThickness="0"
>
<Grid>
<Image Source="/foo;component/Images/Clear-left.png" Stretch="Uniform"/>
</Grid>
</Button>
</Grid>
</StackPanel>
</Grid>
</UserControl>
So here is the usecase, you put this puppy on a tab, select a time & date, navigate off of the tab and return. If you only databind the "Date" proeprty and not the hours and minutes than you find that both of those are set to 0.
The reason for this is that once you navigate off of a tab you dispose of the user controll and when you navigate back you greate a new user controll (.Net 4).
Binding to the Hours and minutes is fugly, and does not amke sense for the consumer as it expects a DateTime object.
I'm tring to figure out what the corect pattern would be for making the hours and minutes reload when the usercontrol gets recreated.
this is how the usercontrol is used in the application
<we:DateTimeRangeElement Date="{Binding Path=Filter.StartTime, Mode=TwoWay}" />
EDIT:
I have a solution, that I don't like, but it will do untill I can get the glue out of the way. what I did was create my own datetime object, and useing that I was able to get a more bindable object.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using ToolSuite.Contract.BaseClasses;
using System.Globalization;
namespace foo.WizardElements
{
/// <summary>
/// Interaction logic for DateTimeRangeElement.xaml
/// </summary>
public partial class DateTimeRangeElement : UserControl
{
public DateTimeRangeElement()
{
InitializeComponent();
this.GotFocus += DateTimeRangeElement_GotFocus;
}
void DateTimeRangeElement_GotFocus(object sender, RoutedEventArgs e)
{
if(Date!=null)
Date.PropertyChanged += Date_PropertyChanged;
}
void Date_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.GetBindingExpression(DateProperty).UpdateSource();
}
private void Clear_Click(object sender, RoutedEventArgs e)
{
Date = null;
}
public static readonly DependencyProperty DateProperty = DependencyProperty.Register("Date",
typeof(FriendlyDateTime),
typeof(DateTimeRangeElement));
public FriendlyDateTime Date
{
get { return (FriendlyDateTime)GetValue(DateProperty); }
set
{
SetValue(DateProperty, value);
}
}
}
}
the xaml
<UserControl x:Class="foo.WizardElements.DateTimeRangeElement"
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"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behavior="clr-namespace:foo.Behaviours"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="myDateTimeControl">
<Grid>
<StackPanel Orientation="Horizontal" Margin="2" Height="24">
<DatePicker x:Name="dp"
VerticalAlignment="top"
Foreground="LightGray"
SelectedDate="{Binding ElementName=myDateTimeControl,Path=Date.Date, Mode=TwoWay}"
BorderBrush="{x:Null}"
>
<DatePicker.Background>
<SolidColorBrush Color="white" Opacity="0.2"/>
</DatePicker.Background>
</DatePicker>
<TextBox x:Name="Hours" Width="30" Text="{Binding ElementName=myDateTimeControl, Path=Date.Hour, Mode=TwoWay, StringFormat=00}">
<i:Interaction.Behaviors>
<behavior:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9]*$" MaxLength="3" />
</i:Interaction.Behaviors>
</TextBox>
<Label Content=":"/>
<TextBox x:Name="Minutes" Width="30" Text="{Binding ElementName=myDateTimeControl, Path=Date.Minute, Mode=TwoWay, StringFormat=00}">
<i:Interaction.Behaviors>
<behavior:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9]*$" MaxLength="3" />
</i:Interaction.Behaviors>
</TextBox>
<Grid HorizontalAlignment="Left" VerticalAlignment="top">
<Button
Background="Transparent"
Click="Clear_Click"
BorderThickness="0"
>
<Grid>
<Image Source="/foo;component/Images/Clear-left.png" Stretch="Uniform"/>
</Grid>
</Button>
</Grid>
</StackPanel>
</Grid>
</UserControl>
the helper
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ToolSuite.Contract.BaseClasses;
namespace foo.WizardElements
{
public class FriendlyDateTime : NotifyPropertyChangedBase
{
public FriendlyDateTime()
{
}
public FriendlyDateTime(DateTime? value)
{
Date = value;
}
public DateTime? Date
{
get
{
if (base._values.ContainsKey("Date"))
return Get<DateTime>("Date");
else
return null;
}
set
{
if (value == null)
{
if (base._values.ContainsKey("Date"))
base._values.Remove("Date");
}
else
Set<DateTime>("Date", value.Value);
base.NotifyPropertyChanged("Date");
base.NotifyPropertyChanged("Hour");
base.NotifyPropertyChanged("Minute");
}
}
public int Hour
{
get { return Date.HasValue ? Date.Value.Hour : 0; }
set
{
if (Hour > 23)
Hour = 23;
var d = Date.HasValue ? Date.Value : DateTime.Now;
Date = new DateTime(d.Year, d.Month, d.Day, value, Minute, 0);
}
}
public int Minute
{
get { return Date.HasValue ? Date.Value.Minute : 0; }
set
{
if (Minute > 59)
Minute = 59;
var d = Date.HasValue ? Date.Value : DateTime.Now;
Date = new DateTime(d.Year, d.Month, d.Day, Hour, value, 0);
}
}
static public implicit operator DateTime?(FriendlyDateTime value)
{
return value.Date;
}
static public implicit operator FriendlyDateTime(DateTime? value)
{
// Note that because RomanNumeral is declared as a struct,
// calling new on the struct merely calls the constructor
// rather than allocating an object on the heap:
return new FriendlyDateTime(value);
}
}
}
the somewhat useless thurd that I'd like to get rid of
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Globalization;
using ToolSuite.Contract.BaseClasses;
namespace foo.WizardElements
{
public class FriendlyDateTimeValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof(DateTime?))
{
return ((FriendlyDateTime)value).Date;
}
else if (targetType == typeof(FriendlyDateTime))
{
return new FriendlyDateTime(value as DateTime?);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof(DateTime?))
{
return ((FriendlyDateTime)value).Date;
}
else if (targetType == typeof(FriendlyDateTime))
{
return new FriendlyDateTime(value as DateTime?);
}
return null;
}
}
}
and lastly the way it is used
<we:DateTimeRangeElement Date="{Binding Path=Filter.EndTime, Mode=TwoWay, Converter={StaticResource DateTimeConverter}, UpdateSourceTrigger=Explicit}" />
Do I like it? No, not really I wish that I could ditch the value converter and the explicit binding. but that will be a research project for an other day.
The solution is MVVM pattern. Your binded data has not to have any relation to a control. Even if you navigate away from the Tab and content controls are disposed, the data remains.
EDIT
I see that in XAML you bind to the properties of your user control. This fundamentaly wrong. You need to consume your data from DataLayer and not from the control. Create a class that is used like a value holder for those properties.
You bind int hour and int min by:
public DateTime StartTime
{
get { return startdate; }
set
{
startdate = new DateTime(value.Year, value.Month, value.Day, startdate.Hour, startdate.Minute, 0);
RaisePropertyChanged("StartTime");
RaisePropertyChanged("StartHour");
RaisePropertyChanged("StartMinute");
}
}
public int StartHour
{
get { return StartTime.Hour; }
set
{
startdate = new DateTime(startdate.Year, startdate.Month, startdate.Day, value, startdate.Minute, 0);
}
}
same for min.... At least that's what I did before with using MVVM, but all these is contains in a data object in my ViewModel.
It depends on if this is a generic UserControl meant to be used by many different applications, or a one-time UserControl meant to be used in one specific case.
If it's a generic control, why not bind Hours/Minutes to the bound Date property? To do that, just bind to DateTimeRangeElement.Date.Hours (or Minutes) instead of DateTimeRangeElement.Hours. If the user is binding a single date data object, they would expect the hours/minutes of that object to get updated when they change the values in your control anyways.
If you don't want to do this, then its up to the user to DataBind Hours/Minutes if they want to keep the value from resetting. It's kind of like using any other UserControl with a TabControl - CheckBox.IsChecked, ListBox.SelectedItem, Expander.IsExpanded, etc all get lost unless they're bound to something.
If this is a one-time UserControl, meant to be used with a specific View and TabControl which you have control over, then just be sure to bind the Hours / Minutes.
The other alternative I've used in the past is to overwrite the TabControl to cache the ContentPresenter when the tab gets unloaded, and to use the cached ContentPresenter instead of re-loading the tab item when it gets switched back. If you want the code for that, it's located in this answer to another question