WPF Edit hours and minutes of a DateTime - wpf

I have 3 TextBoxes to edit a DateTime.
What is important to notice is that those 2 TextBoxes are editing the hour and minutes of the first TextBox DateTime value.
One to edit the Date and 2 to edit hour and minutes.
How would you do that? The code below doesn't reflect the DateTime changes when editing the hour or minute, because it does ToString("HH") and the DateTime value is lost:
<TextBox Text="{Binding MyDateTime}" />
<!--This cannot work : it's just for clearing purposes -->
<TextBox Text="{Binding MyDateTime, StringFormat=\{0:HH\}}}" />
<TextBox Text="{Binding MyDateTime}, StringFormat=\{0:mm\}}" />
Of course I can have a ViewModel as DataContext where I would solve it programmatically.
But I just want to know if there is any possiblity directly in XAML

It is not easily possible with XAML only. There are several possibilities how to solve this:
1. Write custom control or user control that can do this
You could wirte a custom control / user control (e.g. DateTimeTextBox) that has a Property DateTime Value that your xaml can bind against and that contains logic to convert to the datetime value entered in one of its two textboxes. Instead of two textboxes you could also have something like maskedtextbox.
2. Two dedicated properties in the ViewModel
If you go with MVVM you could give your ViewModel two dedicated properties int DateTimeHours int DateTimeMinutes and bind against that:
<TextBox Text="{Binding MyDateTimeHours}" />
<TextBox Text="{Binding MyDateTimeMinutes}" />
Your ViewModel would then merge the two properties to a single DateTime value.

You will need to use a converter with 2way binding and a converter parameter. Save the original hour in the Converter. Then you can update the binding source appropriately.

I appreciate this is an old post, but I came across this today and found another way to handle bindings with date and time.
I created a partial class called dateTime with 4 properties one for the date, 1 for hour and another for minutes. Then a readonly dateTime property that returns a completed date.
Partial Public Class dateTimeValue
Property Dt As Date
Property Hr As Integer
Property Mn As Integer
ReadOnly Property dateTime As Date
Get
Dim d = Dt
d = d.AddHours(d.Hour * -1).AddHours(Hr)
d = d.AddMinutes(d.Minute * -1).AddMinutes(Mn)
d = d.AddSeconds(d.Second * -1)
Return d
End Get
End Property
End Class
Then in the XAML I used a grid layout with the bindings to a DateTimePicker and a couple of ComboBoxes.
<UniformGrid Columns="2">
<TextBlock Text="Date"/>
<DatePicker SelectedDate="{Binding dateTime.Dt}"/>
<TextBlock Text="Time"/>
<WrapPanel>
<ComboBox SelectedValue="{Binding dateTime.Hr}" SelectedValuePath="Content">
<ComboBoxItem>00</ComboBoxItem>
<ComboBoxItem>01</ComboBoxItem>
<ComboBoxItem>02</ComboBoxItem>
.........
<ComboBoxItem>22</ComboBoxItem>
<ComboBoxItem>23</ComboBoxItem>
</ComboBox>
<TextBlock Text=":"/>
<ComboBox SelectedValue="{Binding dateTime.Mn}" SelectedValuePath="Content">
<ComboBoxItem>00</ComboBoxItem>
<ComboBoxItem>15</ComboBoxItem>
<ComboBoxItem>30</ComboBoxItem>
<ComboBoxItem>45</ComboBoxItem>
</ComboBox>
</WrapPanel>
<Button Click="saveTask" Content="Save Task"/>
</UniformGrid>
Then to display the correct date and time in say a textblock you can use
<TextBlock Text="{Binding dateTime.dateTime, StringFormat={}{0:dd MMM yyyy - HH:mm}}"/>
Hope this helps someone else out.

100% MVVM
public class DateTimeConverter : IValueConverter
{
private DateTime _target = DateTime.Now;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var source = value as DateTime?;
if (source is null) return null;
_target = source.Value;
switch (parameter as string)
{
case "y": return source.Value.Year;
case "m": return source.Value.Month;
case "d": return source.Value.Day;
case "h": return source.Value.Hour;
case "i": return source.Value.Minute;
default: return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
switch (parameter as string)
{
case "y": return new DateTime(System.Convert.ToInt32(value), _target.Month, _target.Day, _target.Hour, _target.Minute, 0);
case "m": return new DateTime(_target.Year, System.Convert.ToInt32(value), _target.Day, _target.Hour, _target.Minute, 0);
case "d": return new DateTime(_target.Year, _target.Month, System.Convert.ToInt32(value), _target.Hour, _target.Minute, 0);
case "h": return new DateTime(_target.Year, _target.Month, _target.Day, System.Convert.ToInt32(value), _target.Minute, 0);
case "i": return new DateTime(_target.Year, _target.Month, _target.Day, _target.Hour, System.Convert.ToInt32(value), 0);
default: return _target;
}
}
}

Related

How to bind int to Visibility in WPF?

I have BudgetControlType Properties that has 1 .. 7 value
if(BudgetControlType ==1)
dataComboBox1.Visibility=Visibility.Visiblile;
dataComboBox2 to dataComboBox7 =Visibility.Hidden;
if(BudgetControlType ==2)
dataComboBox1.Visibility=Visibility.Visiblile;
dataComboBox2.Visibility=Visibility.Visiblile;
dataComboBox3 to dataComboBox7 =Visibility.Hidden;
and so on...
How to do this in xaml?
Here is another approach I have used in the past using WPFConverters.
<TabItem.Visibility>
<Binding Path="SomeObservableCollection.Count">
<Binding.Converter>
<converters:ConverterGroup>
<converters:ExpressionConverter Expression="{}{0} > 0" />
<BooleanToVisibilityConverter />
</converters:ConverterGroup>
</Binding.Converter>
</Binding>
</TabItem.Visibility>
The ConvertGroup allows for multiple converters to be run sequentially.
The ExpressionConverter lets you define an arbitrary expression. In my case I want the TabItem to be visible if the collection count is greater than zero. Being defined in xaml means escaping characters and a somewhat awkward syntax but it works well enough!
The BooleanToVisibilityConverter converts the boolean result from the expression to our desired visibility.
For Elham, BudgetControlType could be bound to as long as it implemented INotifyPropertyChanged. An equals expression is done like this (I'm returning true if the bound value equals 7):
<converters:ExpressionConverter Expression="{}{0} == 7" />
You can use 1,2,4,8,... and convert it to Visibility
for example if your int number is 6 (2+4) then Control with paramerter 2 and Control with parameter 4 is Visible!
public class IntToVisibilityConverter:IValueConverter
{
private int val;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int intParam = (int)parameter;
val = (int)value;
return ((intParam & val) != 0) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
And in xaml :
<ComboBox Visibility="{Binding Path=MyEnum,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=1}"/>
<ComboBox Visibility="{Binding Path=MyEnum,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=2}"/>
<ComboBox Visibility="{Binding Path=MyEnum,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=4}"/>
The best way I'd say would be to go with properties on your ViewModel, and bind to them.
example (you'll have to massage it a bit, but it's fairly simple from here) :
public Visibility dtcb1 { get; set; }
// all the rest till 7
// Somewhere in your logit / constructor :
dtcb1 = BudgetControlType == 1 ? Visible : Hidden;
// and so on
And on your xaml you'll bind your visibility to dtcb1
You can make the property boolean, and use a boolean to visibility converter as well (as per this answer for example, or just google yourself)

WPF databinding to display number in an appropriate currency

I need to display a value in a currency format (EUR / USD / YEN etc.) depending on the currency value stored in the database.
In the database the data is stored like:
Id Value Currency
1 1000 EUR
2 1500 USD
3 9650 USD
In XAML, I'd like to know how I can show the value in a correct currency format.
For example, if I read the first line from the database (Id=1), I like to show it on UI as €1,000 but if I read the second line (Id=2) it should be shown as $1,500.
Right now my XAML MVVM binding looks like this:
<TextBlock Text="{Binding SelectedItem, StringFormat=c0}" ...
...and for me this displays the value always as $1,500 which I do not want.
A converter class can do the trick for you to achieve the desired behavior
public class CurrencyConverter : MarkupExtension, IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return GetCurrency(values);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
private string GetCurrency(object[] values)
{
switch (values[1].ToString())
{
case "USD":
return string.Format(new System.Globalization.CultureInfo("en-US"), "{0:C}", values[0]);
case "EUR":
return string.Format(new System.Globalization.CultureInfo("en-GB"), "{0:C}", values[0]);
default:
return string.Format(new System.Globalization.CultureInfo("en-US"), "{0:C}", values[0]);
}
}
}
Simply use the converter in XAML with your TextBlock bindings.
<TextBlock DataContext="{Binding SelectedItem, ElementName=listBox}">
<TextBlock.Text>
<MultiBinding Converter="{local:CurrencyConverter}">
<Binding Path="Value"/>
<Binding Path="Currency"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The string format you're using is based on current system locale so it's not a way to go at all. In your situation you would be interested in something like such a converter: http://blogs.msdn.com/b/bencon/archive/2006/05/10/594886.aspx
Pass in two values (currency and amount), return back a string representation to be shown on UI.

Empty textbox bound with some decimal value

I have a textbox bound to some decimal value. Now, if I type something in it like 100 and clear it completely (empty). I save the data and it saves 1 in decimal value. Similarly if I tried 200 and clear textbox it saves 2. Remember decimal value is not null. Any ideas?
<TextBox
Height="23"
VerticalAlignment="Center"
Text="{Binding Discount,Mode=TwoWay}"
MaxLength="50"
TabIndex="28"
Grid.Row="2"
Grid.Column="1"
local:FocusExtension.IsFocused=
"{Binding Path=IsDiscountFocused,Mode=TwoWay}"
Margin="5,0,0,0"/>
Since String.Empty cannot be converted into a decimal per default. The last valid value is kept in the property.
You should use a converter as below.
Wpf
<Window.Resources>
<local:ValueConverter x:Key="valueConverter" />
</Window.Resources>
<TextBox Text="{Binding Path=Value, Mode=TwoWay, Converter={StaticResource valueConverter}}" />
Converter:
public class ValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string && (string)value == "")
{
return 0;
}
return value;
}
}
Clr:
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
i assume that your Discount property in your viewmodel looks like:
public decimal Discount {get;set;}
if you use backspace and clear your textbox you get a binding exception because "" can not convertet to decimal. so the last number remains in your property. actually this should happen when using UpdateSourceTrigger=Propertychanged. but nevertheless you can try using decimal?. if this not help you can also add a converter to convert the "" to a value your viewmodel can handle.
EDIT: i did not see you last comment. but in the case you want a 0 for an empty textbox, just use a converter to return 0 when "" is the input.

WPF MVVM Light - Binding of SelectedItem not changing

Quite a few posts around this area, but none are helping me... here's the scenario: I've got two "season" drop downs to simulate a range. If you pick a season in the begin range one, the viewmodele automatically sets the property bound to the end range to the same season (so it defaults to a single year and not a range. Here's what the XAML looks like (removed lot of the formatting attibutes for readability):
<ComboBox ItemsSource="{Binding AvailableSeasons, Mode=OneWay}"
SelectedItem="{Binding SelectedBeginRangeSeason, Mode=TwoWay}"
ItemTemplate="{DynamicResource SeasonItemShortFormat}" />
<ComboBox ItemsSource="{Binding AvailableSeasons, Mode=OneWay}"
SelectedItem="{Binding SelectedEndRangeSeason, Mode=TwoWay}"
ItemTemplate="{DynamicResource SeasonItemShortFormat}" />
The properties in the view model look like this:
private Season _selectedBeginRangeSeason;
private const string SelectedBeginRangeSeasonPropertyName = "SelectedBeginRangeSeason";
public Season SelectedBeginRangeSeason {
get { return _selectedBeginRangeSeason; }
set {
if (_selectedBeginRangeSeason != value) {
var oldValue = _selectedBeginRangeSeason;
_selectedBeginRangeSeason = value;
RaisePropertyChanged<Season>(SelectedBeginRangeSeasonPropertyName, oldValue, value, true);
}
}
}
private Season _selectedEndRangeSeason;
private const string SelectedEndRangeSeasonPropertyName = "SelectedEndRangeSeason";
public Season SelectedEndRangeSeason {
get { return _selectedEndRangeSeason; }
set {
if (_selectedEndRangeSeason != value) {
Debug.WriteLine("Updating property SelectedEndRangeSeason...");
var oldValue = _selectedEndRangeSeason;
_selectedEndRangeSeason = value;
Debug.WriteLine("Broadcasting PropertyChanged event for property SelectedEndRangeSeason...");
RaisePropertyChanged<Season>(SelectedEndRangeSeasonPropertyName, oldValue, value, true);
}
}
}
private void UpdateSelectedSeasonSelectors() {
// if the end range isn't selected...
if (_selectedEndRangeSeason == null) {
// automatically select the begin for the end range
SelectedEndRangeSeason = _selectedBeginRangeSeason;
}
}
I've verified the end property is being changed both with the debug statements and with unit tests, but the UI isn't changing when I select it... can't figure out what's going on and have looked at this so many different ways...
Did you get the SelectedSeason from the AvailableSeasons collection? If not, did you implement anything special to compare Seasons?
For example, suppose you have
<ComboBox ItemsSource="{Binding AvailableSeasons}"
SelectedItem="{Binding SelectedSeason}" />
If SelectedSeason = new Season(); the SelectedItem binding won't work because new Season(); does not exist in AvailableSeasons.
You'll need to set SelectedSeason = AvailableSeasons[x] for SelectedItem to work because that makes the two items exactly the same. Or you can implement some custom method to compare the two seasons to see if they're the same. Usually I just overwrite the ToString() method of the class being compared.
Try to fire an event from the ViewModel to notify the UI to refresh the calendar.

Binding to a sum of SelectedItems in WPF GridView

I have a GridView that contains a list of files, created dates, and file sizes. Below the grid I have a textblock that says "X Files Selected. Y MB". I can bind to SelectedItems.Count just fine, but can I easily bind to the sum of the file sizes for those that are selected?
The question marks below should be the sum of the SelectedItems fileSize column values. Any ideas?
<TextBlock HorizontalAlignment="Right">
<TextBlock.Text>
<MultiBinding StringFormat=" {0} Files Selected. {1} MB">
<Binding ElementName="FilesList" Path="SelectedItems.Count"></Binding>
<Binding ElementName="FilesList" Path="SelectedItems.?????"></Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
I know I can get this done in the codebehind - but I'd like to keep my codebehind empty and do it in the XAML. This is the codebehind code:
private void FilesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
double x = 0;
foreach (FileInfo fileInfo in FilesList.SelectedItems)
{
x += fileInfo.Length;
}
}
You're going to have to use a converter for this. An example:
Xaml:
<MultiBinding StringFormat=" {0} Files Selected. {1} MB">
<Binding ElementName="FilesList" Path="SelectedItems.Count"></Binding>
<Binding ElementName="FilesList" Path="SelectedItems" Converter="{StaticResource sumconverter}"></Binding>
</MultiBinding>
Codebehind:
[ValueConversion(typeof(ListViewItem[]), typeof(string))]
class SumConverter : IValueConverter {
public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
int size = 0;
ListViewItem[] items = (ListViewItem[])value;
if(items != null){
foreach(var lvi in items){
Someclass sc = lvi.content as Someclass;
if(sc!=null){
size += sc.Size;
}
}
}
return (size / 1000) + "MB";
}
public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) {
return null;
}
}
Sadly, you will not be able to do this in XAML, alone.
You will need to bind to the SelectedItems themselves and provide a value converter. The value converter needs to iterate over each file path, create a FileInfo object from the path, and sum up the sizes using the FileInfo.Length property.
You have 3 options.
You can create a sum property in whatever entity you are binding to (your FilesList entity). This will require you to change your FilesList collection to a CollectionView so you can get access to the SelectedItems property from your ViewModel (if you aren't doing this already).
I've never tried this, but you might be able to use Kent Boogaart's "Expression Value Converter" that allows you to write small bits of C#-Like code in your binding expressions: http://wpfconverters.codeplex.com/
Provide a simple ValueConverter that converts a collection of whatever your entity is to a decimal or whatever (this is probably the simplest thing to do).

Resources