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
Related
I have a small WPF app that I am working on that uses the Xceed BusyIndicator. I'm having some trouble dynamically updating the loading message because the content is contained within a DataTemplate. The methods I'm familiar with for data binding, or setting the value of text aren't working.
I've done some research - and it looks like others have had this issue. It seems it was answered here, but because the answer was not in context I cannot quite figure out how that would work in my code.
Here is my sample code, if someone could help me understand what I'm missing I would greatly appreciate it. This has the added challenge of using a BackgroundWorker thread. I use this because I anticipate this will be a long running progress - ultimately the action will start a SQL Job that will process items that may take up to 15 minutes. My plan is to have the thread periodically run a stored procedure to get a count of remaining items to process and update the loading message.
MainWindow.xaml:
<Window x:Class="WPFTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:local="clr-namespace:WPFTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<xctk:BusyIndicator x:Name="AutomationIndicator">
<xctk:BusyIndicator.BusyContentTemplate>
<DataTemplate>
<StackPanel Margin="4">
<TextBlock Text="Sending Invoices" FontWeight="Bold" HorizontalAlignment="Center"/>
<WrapPanel>
<TextBlock Text="Items remaining: "/>
<TextBlock x:Name="_ItemsRemaining" Text="{Binding Path=DataContext.ItemsRemaining, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
</WrapPanel>
</StackPanel>
</DataTemplate>
</xctk:BusyIndicator.BusyContentTemplate>
<Grid>
<StackPanel>
<TextBlock Text="Let's test this thing" />
<Button x:Name="_testBtn" Content="Start" Width="100" HorizontalAlignment="Left" Click="testBtn_Click"/>
<TextBlock Text="{Binding ItemsRemaining}"/>
</StackPanel>
</Grid>
</xctk:BusyIndicator>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
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 WPFTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
UpdateItemsRemaining(0);
}
public class ItemCountDown
{
//One Idea was to try and set a data binding variable
public string ItemsRemaining { get; set; }
}
public void UpdateItemsRemaining(int n)
{
ItemCountDown s = new ItemCountDown();
{
s.ItemsRemaining = n.ToString();
};
//this.AutomationIndicator.DataContext = s; Works during initiation, but not in the thread worker.
}
private void testBtn_Click(object sender, RoutedEventArgs e)
{
//Someone clicked the button, run the Test Status
TestStatus();
}
public void TestStatus()
{
// Normally I'd start a background worker to run a loop to check that status in SQL
BackgroundWorker getStatus = new BackgroundWorker();
getStatus.DoWork += (o, ea) =>
{
//Normally there's a sql connection being opened to check a SQL Job, and then I run a loop that opens the connection to check
//the status until it either fails or successfully ended.
//but for this test, I'll just have it run for 15 seconds, counting down fake items.
int fakeItems = 8;
do //
{
//Idea One - write to the text parameter. But can't find it in the template
//Dispatcher.Invoke((Action)(() => _ItemsRemaining.Text = fakeItems));
//Idea two - use data binding to update the value. Data binding works just find outside of the Data Template but is ignored in the template
UpdateItemsRemaining(fakeItems);
//subtract one from fake items and wait a second.
fakeItems--;
Thread.Sleep(1000);
} while (fakeItems > 0);
};
getStatus.RunWorkerCompleted += (o, ea) =>
{
//work done, end it.
AutomationIndicator.IsBusy = false;
};
AutomationIndicator.IsBusy = true;
getStatus.RunWorkerAsync();
}
}
}
Thank you for reviewing and I appreciate any help or direction given.
Set the DataContext to your ItemCountDown object and implement INotifyPropertyChanged:
public class ItemCountDown : INotifyPropertyChanged
{
private string _itemsRemaining;
public string ItemsRemaining
{
get { return _itemsRemaining; }
set { _itemsRemaining = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public partial class MainWindow : Window
{
private readonly ItemCountDown s = new ItemCountDown();
public MainWindow()
{
InitializeComponent();
DataContext = s;
UpdateItemsRemaining(0);
}
public void UpdateItemsRemaining(int n)
{
s.ItemsRemaining = n.ToString();
}
private void testBtn_Click(object sender, RoutedEventArgs e)
{
TestStatus();
}
public void TestStatus()
{
...
}
}
You can then bind directly to the property in the DataTemplate of the XAML markup:
<TextBlock x:Name="_ItemsRemaining" Text="{Binding Path=DataContext.ItemsRemaining, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
Very new to WPF coding using MVVM. Tried making a simple calculator in WPF using MVVM. But unable to trigger the Icommand in the below code.If possible help me in this. Grateful if anybody can help me out.
View Code:
<Window x:Class="MVVMCalculator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MVVMCalculator"
mc:Ignorable="d"
Title="Calculator" Height="350" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="85"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Display, Mode=OneWay}" IsReadOnly="True" TextWrapping="Wrap"
Grid.Row="0" Background="#E2E2E2" Margin="0,10,0,0" VerticalAlignment="Top"
Height="75" Width="250" HorizontalAlignment="Center" FontSize="22" FontWeight="Bold"
TextAlignment="Right">
<TextBox.Effect>
<DropShadowEffect/>
</TextBox.Effect>
</TextBox>
<ItemsControl Grid.Row="1" ItemsSource="{Binding Buttns}" Margin="15,15,15,10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" Rows="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Txt, Mode=TwoWay}" Command="{Binding Enter_number}"
FontSize="18" FontWeight="Bold" Height="50" Width="50" Background="#eef2f3"
BorderBrush="Black" BorderThickness="1.0" Name="number">
<Button.Effect>
<DropShadowEffect/>
</Button.Effect>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
ViewModel Code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MVVMCalculator
{
class ViewModel : INotifyPropertyChanged
{
Buttons btn = new Buttons();
private decimal operand1;
private decimal operand2;
private string operation;
private decimal result;
private string display;
private bool newDisplayRequired = false;
ObservableCollection<Buttons> buttns;
public ObservableCollection<Buttons> Buttns
{
get { return buttns; }
set { buttns = value; }
}
public decimal Result
{
get { return result; }
}
public decimal Operand1
{
get { return operand1; }
set { operand1 = value; }
}
public decimal Operand2
{
get { return operand2; }
set { operand2 = value; }
}
public string Operation
{
get { return operation; }
set { operation = value; }
}
public string Display
{
get { return display; }
set { display = value;
OnPropertyChanged("Display");
}
}
public ViewModel()
{
buttns = new ObservableCollection<Buttons>
{
new Buttons("1"), new Buttons("2"), new Buttons("3"),
new Buttons("C"), new Buttons("Back"), new Buttons("4"),
new Buttons("5"), new Buttons("6"), new Buttons("CE"),
new Buttons("%"), new Buttons("7"), new Buttons("8"),
new Buttons("9"), new Buttons("/"), new Buttons("*"),
new Buttons("0"), new Buttons("."), new Buttons("+"),
new Buttons("-"), new Buttons("=")
};
display = "0";
operand1 = 0;
operand2 = 0;
operation = "";
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ICommand enter_number;
public ICommand Enter_number
{
get
{
if(enter_number==null)
{
enter_number = new DelegateCommand<string>(MyAction, _canExecute);
}
return enter_number;
}
}
private static bool _canExecute(string button)
{
return true;
}
public void MyAction(string btn)
{
switch(btn)
{
case "C":
display = "0";
operand1 = 0;
operand2 = 0;
//operation = "";
break;
case ".":
if (!display.Contains("."))
{
Display = display + ".";
}
break;
case "Back":
if (display.Length > 1)
Display = display.Substring(0, display.Length - 1);
else Display = "0";
break;
default:
if (display == "0" || newDisplayRequired)
Display = btn;
else
Display = display + btn;
break;
}
}
}
}
Buttons Class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMCalculator
{
class Buttons:INotifyPropertyChanged
{
private string txt;
public string Txt
{
get { return txt; }
set { txt = value; }
}
public Buttons(string a)
{
txt = a;
}
public Buttons()
{
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Xaml.cs:
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 MVVMCalculator
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
}
Since the Enter_number property is defined in the ViewModel class you need to use a {RelativeSource} to be able to bind to it:
<Button Content="{Binding Txt, Mode=TwoWay}"
Command="{Binding DataContext.Enter_number, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
FontSize="18" FontWeight="Bold" Height="50" Width="50" Background="#eef2f3"
BorderBrush="Black" BorderThickness="1.0" Name="number">
<Button.Effect>
<DropShadowEffect/>
</Button.Effect>
</Button>
The default DataContext of the Button is the current Buttons object in the ItemsSource collection of the ItemsControl and that's why your binding fails.
This question already has answers here:
How do I update an ObservableCollection via a worker thread?
(7 answers)
Closed 5 years ago.
I try to do some simple task (I guess so). I want to change GUI dynamically, from the for loop.
Let's see my XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel Name="MyPanel">
<TextBlock Text="{Binding MyValue}"></TextBlock>
<Button Click="Button_Click">OK</Button>
<ListBox Name="myList" ItemsSource="{Binding MyCollection}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding A}" Grid.Column="0"/>
<TextBlock Text="{Binding B}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
As you can see, I have the Textblock that shows numbers, button that is starting the program and the listbox, that should show the collection items.
After click on the button, the first textblock (bindes MyValue) shows dynamic values, but on the list box I get the next error:
"This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread."
I saw another answers for the error, but cannot understand how to implement it in my case.
Here the C# code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
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 WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public static MyModel m;
public MainWindow()
{
m = new MyModel();
InitializeComponent();
MyPanel.DataContext = m;
}
bool flag = false;
private void Button_Click(object sender, RoutedEventArgs e)
{
flag = !flag;
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 5000000; i++)
{
if (flag == false) break;
m.MyValue = i.ToString();
m.MyCollection.Add(new ChartPoint { A = i, B = 2 * i });
}
});
}
}
public class MyModel : INotifyPropertyChanged
{
private string myValue;
public ObservableCollection<ChartPoint> MyCollection { get; set; }
public MyModel()
{
MyCollection = new ObservableCollection<ChartPoint>();
}
public string MyValue
{
get { return myValue; }
set
{
myValue = value;
RaisePropertyChanged("MyValue");
}
}
private void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ChartPoint
{
public int A { get; set; }
public int B { get; set; }
}
}
Thanks a lot!
I have changed the Button_Click code a bit, is this you want to achieve, please suggest:
private void Button_Click(object sender, RoutedEventArgs e)
{
flag = !flag;
var list = new List <ChartPoint>();
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 50000000; i++)
{
if (flag == false) break;
m.MyValue = i.ToString();
Dispatcher.BeginInvoke(new Action(() =>
{
m.MyCollection.Add(new ChartPoint
{
A = i,
B = 2 * i
});
}),
DispatcherPriority.Background);
}
});
}
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();
}
}
}
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.