I created a ControlTemplate for my custom control MyControl.
MyControl derives from System.Windows.Controls.Control and defines the following property public ObservableCollection<MyControl> Children{ get; protected set; }.
To display the nested child controls I am using an ItemsControl (StackPanel) which is surrounded by a GroupBox. If there are no child controls, I want to hide the GroupBox.
Everything works fine on application startup: The group box and child controls are shown if the Children property initially contained at least one element. In the other case it is hidden.
The problem starts when the user adds a child control to an empty collection. The GroupBox's visibility is still collapsed. The same problem occurs when the last child control is removed from the collection. The GroupBox is still visible.
Another symptom is that the HideEmptyEnumerationConverter converter does not get called.
Adding/removing child controls to non empty collections works as expected.
Whats wrong with the following binding? Obviously it works once but does not get updated, although the collection I am binding to is of type ObservableCollection.
<!-- Converter for hiding empty enumerations -->
<Common:HideEmptyEnumerationConverter x:Key="hideEmptyEnumerationConverter"/>
<!--- ... --->
<ControlTemplate TargetType="{x:Type MyControl}">
<!-- ... other stuff that works ... -->
<!-- Child components -->
<GroupBox Header="Children"
Visibility="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Children, Converter={StaticResource hideEmptyEnumerationConverter}}">
<ItemsControl ItemsSource="{TemplateBinding Children}"/>
</GroupBox>
</ControlTemplate>
.
[ValueConversion(typeof (IEnumerable), typeof (Visibility))]
public class HideEmptyEnumerationConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int itemCount = ((IEnumerable) value).Cast<object>().Count();
return itemCount == 0 ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Another, more general question: How do you guys debug bindings? Found this (http://bea.stollnitz.com/blog/?p=52) but still I find it very hard to do.
I am glad for any help or suggestion.
The problem is that your Children property itself never changes, just its content. Since the property value doesn't change, the binding isn't reevaluated. What you need to do is bind to the Count property of the collection. The easiest way you can achieve this is with a DataTrigger in your template:
<ControlTemplate TargetType="{x:Type MyControl}">
<!-- ... other stuff that works ... -->
<!-- Child components -->
<GroupBox x:Name="gb" Header="Children">
<ItemsControl ItemsSource="{TemplateBinding Children}"/>
</GroupBox>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Children.Count, RelativeSource={RelativeSource TemplatedParent}}"
Value="0">
<Setter TargetName="gb" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
You need to notify whenever the number of items in your Children property changes. You can do that by implementing INotifyPropertyChanged interface, register to Children collection's CollectionChanged event and raise PropertyChanged from there.
Example:
public class MyControl : Control, INotifyPropertyChanged
{
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
}
public ObservableCollection<UIElement> Children
{
get { return (ObservableCollection<UIElement>)GetValue(ChildrenProperty); }
set { SetValue(ChildrenProperty, value); }
}
// Using a DependencyProperty as the backing store for Children. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ChildrenProperty =
DependencyProperty.Register("Children", typeof(ObservableCollection<UIElement>), typeof(MyControl), new UIPropertyMetadata(0));
public MyControl()
{
Children = new ObservableCollection<UIElement>();
Children.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Children_CollectionChanged);
}
void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("Children");
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(String propertyName)
{
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Related
I'm using the Delay binding tag of .Net 4.5 but I want to change the textbox's background color while the changes are not "committed". How can I set an IsDirty property to true while the delay is happening?
I tried using the TextChanged event to set an IsDirty flag and then remove the flag when the bound property got set. The problem is that the TextChanged fires whenever the bound property changes and not just when the user modifies the text.
I got it "working" in a very clunky and fragile way by monitoring the TextChanged event and the bound property. Needless to say this is very prone to bugs so I would like a cleaner solution. Is there any way to know that the textbox has been changed but not committed yet (by the Delay)?
I had a look through the source code and the BindingExpressionBase itself is aware of this through a property called NeedsUpdate. But this property is internal so you would have to use reflection to get it.
However, you won't be able to monitor this property in any easy way. So the way I see it, you would need to use both of the events TextChanged and SourceUpdated to know when NeedsUpdate might have changed.
Update
I created an attached behavior that does this, it can be used to monitor pending updates on any DependencyProperty. Note that NotifyOnSourceUpdated must be set to true.
Uploaded a small sample project here: PendingUpdateExample.zip
Example
<TextBox Text="{Binding ElementName=textBoxSource,
Path=Text,
NotifyOnSourceUpdated=True,
UpdateSourceTrigger=PropertyChanged,
Delay=1000}"
ab:UpdatePendingBehavior.MonitorPendingUpdates="{x:Static TextBox.TextProperty}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="ab:UpdatePendingBehavior.HasPendingUpdates"
Value="True">
<Setter Property="Background" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
UpdatePendingBehavior
public class UpdatePendingBehavior
{
#region MonitorPendingUpdates
public static DependencyProperty MonitorPendingUpdatesProperty =
DependencyProperty.RegisterAttached("MonitorPendingUpdates",
typeof(DependencyProperty),
typeof(UpdatePendingBehavior),
new UIPropertyMetadata(null, MonitorPendingUpdatesChanged));
public static DependencyProperty GetMonitorPendingUpdates(FrameworkElement obj)
{
return (DependencyProperty)obj.GetValue(MonitorPendingUpdatesProperty);
}
public static void SetMonitorPendingUpdates(FrameworkElement obj, DependencyProperty value)
{
obj.SetValue(MonitorPendingUpdatesProperty, value);
}
public static void MonitorPendingUpdatesChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
DependencyProperty property = e.NewValue as DependencyProperty;
if (property != null)
{
FrameworkElement element = target as FrameworkElement;
element.SourceUpdated += elementProperty_SourceUpdated;
if (element.IsLoaded == true)
{
SubscribeToChanges(element, property);
}
element.Loaded += delegate { SubscribeToChanges(element, property); };
element.Unloaded += delegate { UnsubscribeToChanges(element, property); };
}
}
private static void SubscribeToChanges(FrameworkElement element, DependencyProperty property)
{
DependencyPropertyDescriptor propertyDescriptor =
DependencyPropertyDescriptor.FromProperty(property, element.GetType());
propertyDescriptor.AddValueChanged(element, elementProperty_TargetUpdated);
}
private static void UnsubscribeToChanges(FrameworkElement element, DependencyProperty property)
{
DependencyPropertyDescriptor propertyDescriptor =
DependencyPropertyDescriptor.FromProperty(property, element.GetType());
propertyDescriptor.RemoveValueChanged(element, elementProperty_TargetUpdated);
}
private static void elementProperty_TargetUpdated(object sender, EventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
UpdatePendingChanges(element);
}
private static void elementProperty_SourceUpdated(object sender, DataTransferEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (e.Property == GetMonitorPendingUpdates(element))
{
UpdatePendingChanges(element);
}
}
private static void UpdatePendingChanges(FrameworkElement element)
{
BindingExpressionBase beb = BindingOperations.GetBindingExpressionBase(element, GetMonitorPendingUpdates(element));
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
PropertyInfo needsUpdateProperty = beb.GetType().GetProperty("NeedsUpdate", bindingFlags);
SetHasPendingUpdates(element, (bool)needsUpdateProperty.GetValue(beb));
}
#endregion // MonitorPendingUpdates
#region HasPendingUpdates
public static DependencyProperty HasPendingUpdatesProperty =
DependencyProperty.RegisterAttached("HasPendingUpdates",
typeof(bool),
typeof(UpdatePendingBehavior),
new UIPropertyMetadata(false));
public static bool GetHasPendingUpdates(FrameworkElement obj)
{
return (bool)obj.GetValue(HasPendingUpdatesProperty);
}
public static void SetHasPendingUpdates(FrameworkElement obj, bool value)
{
obj.SetValue(HasPendingUpdatesProperty, value);
}
#endregion // HasPendingUpdates
}
Another way could be to use a MultiBinding that binds both to the source and the target and compares their values in a converter. Then you could change the Background in the Style. This assumes that you don't convert the value. Example with two TextBoxes
<TextBox Text="{Binding ElementName=textBoxSource,
Path=Text,
UpdateSourceTrigger=PropertyChanged,
Delay=2000}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsTextEqualConverter}">
<Binding RelativeSource="{RelativeSource Self}"
Path="Text"/>
<Binding ElementName="textBoxSource" Path="Text"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox Name="textBoxSource"/>
IsTextEqualConverter
public class IsTextEqualConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].ToString() == values[1].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You could try some other event like PreviewTextInput or one of the key related ones. (You probably need the tunneling versions as bubbling events are probably handled internally)
I'm not sure about the Delay binding. However, in .Net 4.0 I'd use a BindingGroup. BindingGroup has a CanRestoreValues property which will report exactly what you want: If the UpdateSourceTrigger is Explicit (which is default if there is a BindingGroup), CanRestoreValues will be true from the time one bound control value has been changed until BindingGroup.CommitEdit is called and the values are forwarded to the bound object. However, CanRestoreValues will be true as soon as any control that shares the BindingGroup has a pending value, so if you want feedback for each separate property you will have to use one BindingGroup per control, which makes calling CommitEdit a little bit less convenient.
I'm trying to make a custom converter that inherits from DependencyObject, but it doesn't work:
Converter:
public class BindingConverter : DependencyObject , IValueConverter
{
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(BindingConverter), new PropertyMetadata(null));
public object Convert(object value, Type targetType, object parameter, Globalization.CultureInfo culture)
{
Debug.Assert(Value != null); //fails
return Value;
}
public object ConvertBack(object value, Type targetType, object parameter, Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Xaml:
<StackPanel x:Name="this">
<!--works-->
<ContentControl Content="{Binding ActualHeight, ElementName=this}"/>
<!--doesn't work-->
<ContentControl>
<Binding>
<Binding.Converter>
<BindingConverter Value="{Binding ActualHeight, ElementName=this}" />
</Binding.Converter>
</Binding>
</ContentControl>
<TextBlock Text="{Binding Animals}"/>
</StackPanel>
Am I missing out anything?
I have some places in my projects where I needed similar functionality. Can't show you exact sample, just an idea:
perhaps you have to inherit from FrameworkElement, not IValueConverter, Something like this:
public class BindingHelper : FrameworkElement
in the BindingHelper class, set Visibility to Collapsed and IsHitTestVisible to false;
to make it working, insert it into visual tree directly. In your example, it should be a child of the StackPanel. So, it will have the same DataContext as other StackPanel children;
then, you can add one ore more dependency properties depending on your needs. For example, you might have single property for the source of data and some different properties which you then will use as converter return values. Handle all changes to the source property in your BindingHelper class and change output properties accordingly;
bind other controls to properties of the BindingHelper class using ElementName syntax
in Silverlight, ActualHeight and ActualWidth properties don't do notifications on property updates. So, binding to them won't work.
Note! ActualHeight property's binding is buggy on binding!
Why you inherit DependencyObject when coding a converter? You should just implement IValueConverter.
Try that,
First add MyConverter by the key of "MyConverterResource" on your resources then,
You can do than on XAML side or on cs side by
//You may do it on XAML side <UserControl.Resources>...
this.Resources.Add("MyConverterResource",new MyConverter());
<TextBlock Text="{Binding ActualHeight,ElementName=this
,Converter=MyConverterResource}"/>
public class MyConverter: IValueConverter
{
public object Convert(object value, Type targetType
, object parameter,Globalization.CultureInfo culture)
{
return "Your Height is:"+Value.toString();
}
}
Hope helps
I have a Silverlight Templated Control (not a user control), which contains a ListBox.
In the DataTemplate of the ListBox i have a Button, like so:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ProgressBar Grid.Column="0" Width="70" Height="20" Value="{Binding Path=Percentage}" Minimum="0.0" Maximum="100.0" />
<TextBlock Grid.Column="0" Text="{Binding Path=Percentage, StringFormat='{}{0:##0.0}%'}" Margin="10,3,3,3" HorizontalAlignment="Center" />
<TextBlock Grid.Column="1" Text="{Binding Path=File.Name}" Margin="3" />
<Button Grid.Column="2" Content="Remove" x:Name="RemoveButton" Command="{TemplateBinding DeleteCommand}" Style="{TemplateBinding UploadButtonStyle}" HorizontalAlignment="Right" Margin="0,0,5,0" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
See the button there at the end of the template? HOW CAN I ACCESS IT'S CLICK EVENT? I can't use the GetTemplateChild() method since the button is part of the DataTemplate. I've tried Commanding (as you can see above). Seems like that's the way to go, although the Templated Control isn't exactly MVVM.
Any ideas? Maybe something other than Commanding? or else I'm doing the commanding wrong?
here's some relevant code:
...the Dependency Property / Property definitions... (should it be a Dep Prop?)
public static readonly DependencyProperty DeleteCommandProperty =
DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(MultipleFileUpload), new PropertyMetadata(null));
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set
{
SetValue(DeleteCommandProperty, value);
FirePropertyChanged("DeleteCommand"); //INotifyPropertyChanged stuff
}
}
... in OnApplyTemplate()...
public override void OnApplyTemplate()
{
....
DeleteCommand = new DelegateCommand(RemoveItemFromList, CanRemove);
....
base.OnApplyTemplate();
}
...the ICommand Action...
private void RemoveItemFromList(object commandParameter)
{
//NEVER GETTING HERE!
}
I hope it's something small.
Thanks people!
Kevin
I've added a command as a property to the class of the objects I bind into ListBoxes's (and other ItemsControl's) ItemSource. This does mean I have to change my "data" objects to handle GUI events - which often seemed wrong and hacky.
I've also derived ItemsControl (but since a listbox is an ItemsControl this may still apply). I add my own properties the derived control that I'll ultimately want to access from the items. In your case the button command handler. It should be easy to set these properties since they aren't locked-up in that nested template.
Next, I overrided GetContainerForItemOverride() in that derived class and return another class, my own derived ContentPresenter. This new ContentPresenter should also have that same command property - set it equal to ItemControl's command in GetContainerForItemOverride when you construct it.
Now in the DataTemplate use TemplateBinding (not regular Binding) to get to that Command.
I've kicked around the item of trying to make a generic/reusable version of all of this.
Edit, basic example :
class MyItemsControl : ItemsControl
{
public Command MyCommand {get;set;} // I've often use a full-blown DP here
snip
protected override DependencyObject GetContainerForItemOverride()
{
return new MyContentPresenter(this.MyCommand); // MyContentPresenter is just a derived ContentPresenter with that same property.
}
Edit again:
I've also put code in ItemsControl.PrepareContainerForItemOverride. This method gives you both the ContentControl (your own one if you're overriding GetContainerForItemOverride) and the current "Item" in the list. In here you can also do further initialization of the ContentControl instance - if what you want to do depends on the object that it's being bound to.
I suggest you use a single relaycommand:
public class RelayCommand<T> : ICommand
{
#region Fields
readonly Action<T> _execute = null;
readonly Predicate<T> _canExecute = null;
#endregion // Fields
#region Constructors
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion // ICommand Members
}
XAML:
<Button Grid.Column="2" Content="Remove" x:Name="RemoveButton" Command="{Binding DeleteCommand}" CommandParameter={Binding} Style="{TemplateBinding UploadButtonStyle}" HorizontalAlignment="Right" Margin="0,0,5,0" />
what this will do is everytime you click on the button, it will invoke the same deletecommand, but will pass the current item as parameter.
Hope this helps
I've come across this idea in MSDN, I have not tried it but I figured it was worth sharing here:
The DataContext of the items in the list box is not the same as the views DataContext. Each item's DataContext refers to an item in the collection that is bound to the list box's ItemsSource property.
A solution is to bind the command property to a static resource and set the value of the static resource to the command you want to bind. This is illustrated in the following XAML from the Stock Trader RI.
<!--Specifying the observablecommand in the view's resources-->
<UserControl.Resources>
<Infrastructure:ObservableCommand x:Key="BuyCommand" />
</UserControl.Resources>
<!—Binding the Button Click to the command. This control can sit inside a datagrid or a list box. -->
<Button Commands:Click.Command="{Binding Path=Value, Source={StaticResource BuyCommand}}" Commands:Click.CommandParameter="{Binding Path=TickerSymbol}" />
Then in the code-behind of the view, you must specify that the value of the resource actually points to the command on the presentation model. The following is an example of this from the Stock Trader RI, where the BuyCommand property on the presentation model is put in the resources.
((ObservableCommand)this.Resources["BuyCommand"]).Value = value != null ? value.BuyCommand : null;
Hi you can use relative source and AncesterType. Then its works fine for me.
Refer the below code.
<Button Content="Delete" Command="{Binding DataContext.DeleteCommand,
RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}"
CommandParameter="{Binding Path=SelectedItem, RelativeSource= {RelativeSource FindAncestor, AncestorType=
{x:Type ListBox}}}"/>
Is it possible to nest a Progress bar into a combobox or the other way around. I want to be able to type into the combo box and hit a button and the progress bar shows the progress of the event, like in Windows Explorer.
EDIT: I need the code in Visual Basic.NET 3.5 Thanks.
Here's one way to do it, basically what I've done is:
Subclass ComboBox and add IsProgressVisible and ProgressValue dependency properties
Add a green rectangle the the ComboBox control template exactly behind the editable area
Bind the rectangle visibility to IsProgressVisible and the rectangle width (using a ScaleTransform) to ProgressValue
First the new control code:
public class ProgressCombo : ComboBox
{
public static readonly DependencyProperty IsProgressVisibleProperty =
DependencyProperty.Register("IsProgressVisible", typeof(bool), typeof(ProgressCombo));
public bool IsProgressVisible
{
get { return (bool)GetValue(IsProgressVisibleProperty); }
set { SetValue(IsProgressVisibleProperty, value); }
}
public static readonly DependencyProperty ProgressValueProperty =
DependencyProperty.Register("ProgressValue", typeof(double), typeof(ProgressCombo));
public double ProgressValue
{
get { return (double)GetValue(ProgressValueProperty); }
set { SetValue(ProgressValueProperty, value); }
}
}
There's also a value converter we'll use:
public class FromPercentConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((double)value) / 100;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Now take the combo box sample style from MSDN (.net 3.5 version, not 4) from http://msdn.microsoft.com/en-us/library/ms750638%28VS.90%29.aspx
Add an xmlns:l definition to your own assembly
Now change <Style x:Key="{x:Type ComboBox}" TargetType="ComboBox"> to <Style x:Key="{x:Type l:ProgressCombo}" TargetType="l:ProgressCombo">
Change <ControlTemplate TargetType="l:ComboBox"> To:
<ControlTemplate TargetType="l:ProgressCombo">
<ControlTemplate.Resources>
<BooleanToVisibilityConverter x:Key="Bool2Vis"/>
<l:FromPercentConverter x:Key="FromPercent"/>
</ControlTemplate.Resources>
Locate the line <ContentPresenter and add before it:
<Rectangle
Fill="LightGreen"
Margin="3,3,23,3"
Visibility="{TemplateBinding IsProgressVisible, Converter={StaticResource Bool2Vis}}">
<Rectangle.RenderTransform>
<ScaleTransform ScaleX="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ProgressValue, Converter={StaticResource FromPercent}}"/>
</Rectangle.RenderTransform>
</Rectangle>
And that's it
I had a similar sort of requirement for a different reason (i had a combo that was auto-populating after a network scan). See if this question & answer helps you: WPF ComboBox - showing something different when no items are bound
I have a class EmployeeViewModel with 2 properties "FirstName" and "LastName". The class also has a dictionary with the changes of the properties. (The class implements INotifyPropertyChanged and IDataErrorInfo, everything is fine.
In my view there is a textbox:
<TextBox x:Name="firstNameTextBox" Text="{Binding Path=FirstName}" />
How can I change the background color of the textbox, if the original value changed? I thought about creating a trigger which sets the background color but to what should I bind?
I don't want to created an additional property for every control which holds the state wheter the one was changed or not.
Thx
Just use a MultiBinding with the same property twice but have Mode=OneTime on one of the bindings. Like this:
Public Class MVCBackground
Implements IMultiValueConverter
Public Function Convert(ByVal values() As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IMultiValueConverter.Convert
Static unchanged As Brush = Brushes.Blue
Static changed As Brush = Brushes.Red
If values.Count = 2 Then
If values(0).Equals(values(1)) Then
Return unchanged
Else
Return changed
End If
Else
Return unchanged
End If
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetTypes() As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
And in the xaml:
<TextBox Text="{Binding TestText}">
<TextBox.Background>
<MultiBinding Converter="{StaticResource BackgroundConverter}">
<Binding Path="TestText" />
<Binding Path="TestText" Mode="OneTime" />
</MultiBinding>
</TextBox.Background>
</TextBox>
No extra properties or logic required and you could probably wrap it all into your own markup extension. Hope that helps.
You will need to use a value converter (converting string input to color output) and the simplest solution involves adding at least one more property to your EmployeeViewModel. You need to make some sort of a Default or OriginalValue property, and compare against that. Otherwise, how will you know what the "original value" was? You cannot tell if the value changed unless there is something holding the original value to compare against.
So, bind to the text property and compare the input string to the original value on the view model. If it has changed, return your highlighted background color. If it matches, return the normal background color. You will need to use a multi-binding if you want to compare the FirstName and LastName together from a single textbox.
I have constructed an example that demonstrates how this could work:
<Window x:Class="TestWpfApplication.Window11"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApplication"
Title="Window11" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<local:ChangedDefaultColorConverter x:Key="changedDefaultColorConverter"/>
</Window.Resources>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock>Default String:</TextBlock>
<TextBlock Text="{Binding Path=DefaultString}" Margin="5,0"/>
</StackPanel>
<Border BorderThickness="3" CornerRadius="3"
BorderBrush="{Binding ElementName=textBox, Path=Text, Converter={StaticResource changedDefaultColorConverter}}">
<TextBox Name="textBox" Text="{Binding Path=DefaultString, Mode=OneTime}"/>
</Border>
</StackPanel>
And here is the code-behind for the Window:
/// <summary>
/// Interaction logic for Window11.xaml
/// </summary>
public partial class Window11 : Window
{
public static string DefaultString
{
get { return "John Doe"; }
}
public Window11()
{
InitializeComponent();
}
}
Finally, here is the converter you use:
public class ChangedDefaultColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = (string)value;
return (text == Window11.DefaultString) ?
Brushes.Transparent :
Brushes.Yellow;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And even though I wrapped a border around the TextBox (because I think that looks a little better), the Background binding can be done exactly the same way:
<TextBox Name="textBox" Text="{Binding Path=DefaultString, Mode=OneTime}"
Background="{Binding ElementName=textBox, Path=Text, Converter={StaticResource changedDefaultColorConverter}}"/>
If you're using the MVVM paradigm, you should consider the ViewModels as having the role of adapters between the Model and the View.
It is not expected of the ViewModel to be completely agnostic of the existence of a UI in every way, but to be agnostic of any specific UI.
So, the ViewModel can (and should) have the functionality of as many Converters as possible. The practical example here would be this:
Would a UI require to know if a text is equal to a default string?
If the answer is yes, it's sufficient reason to implement an IsDefaultString property on a ViewModel.
public class TextViewModel : ViewModelBase
{
private string theText;
public string TheText
{
get { return theText; }
set
{
if (value != theText)
{
theText = value;
OnPropertyChanged("TheText");
OnPropertyChanged("IsTextDefault");
}
}
}
public bool IsTextDefault
{
get
{
return GetIsTextDefault(theText);
}
}
private bool GetIsTextDefault(string text)
{
//implement here
}
}
Then bind the TextBox like this:
<TextBox x:Name="textBox" Background="White" Text="{Binding Path=TheText, UpdateSourceTrigger=LostFocus}">
<TextBox.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsTextDefault}" Value="False">
<Setter Property="TextBox.Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Resources>
</TextBox>
This propagates text back to the ViewModel upon TextBox losing focus, which causes a recalculation of the IsTextDefault. If you need to do this a lot of times or for many properties, you could even cook up some base class like DefaultManagerViewModel.
You could add to your ViewModel boolean properties like IsFirstNameModified and IsLastNameModified, and use a trigger to change the background if the textbox according to these properties. Or you could bind the Background to these properties, with a converter that returns a Brush from a bool...
A complete diferent way would be to not implement INotifyPropertyChanged and instead descend from DependencyObject or UIElement
They implement the binding using DependencyProperty
You may event use only one event handler and user e.Property to find the rigth textbox
I'm pretty sure the e.NewValue != e.OldValue check is redundant as the binding should not have changed. I also beleive there may be a way to implement the binding so the dependecyObject is the textbox and not your object...
Edit if you already inherit from any WPF class (like control or usercontrol) you are probably ok and you don't need to change to UIElement as most of WPF inherit from that class
Then you can have:
using System.Windows;
namespace YourNameSpace
{
class PersonViewer:UIElement
{
//DependencyProperty FirstName
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register("FirstName", typeof (string), typeof (PersonViewer),
new FrameworkPropertyMetadata("DefaultPersonName", FirstNameChangedCallback));
public string FirstName {
set { SetValue(FirstNameProperty, value); }
get { return (string) GetValue(FirstNameProperty); }
}
private static void FirstNameChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) {
PersonViewer owner = d as PersonViewer;
if (owner != null) {
if(e.NewValue != e.OldValue && e.NewValue != "DefaultPersonName" ) {
//Set Textbox to changed state here
}
}
}
public void AcceptPersonChanges() {
//Set Textbox to not changed here
}
}
}
A variation of the last answer could be to alwais be in the modified state unless the value is the default value.
<TextBox.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="IsLoaded" Value="True">
<Setter Property="TextBox.Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource Self}, Path=Text" Value="DefaultValueHere">
<Setter Property="TextBox.Background" Value=""/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Resources>