I have a bunch of textblock objects in my program, and the foreground color of all of these is white. I want to find out what causes it to be this way.
Is there any means in Visual Studio of tracing back the origin of a property value, whether by template or containing object?
EDIT:
After issuing a bounty, this question got a lot of interesting responses with a variety of proposed approaches. I think there's something there, but so far I have not been able to figure out how to apply any of these more effectively than just trial and error hunting through the code. If anybody wants to take up the charge and advance one of the responses as useful, I'm watching.
I tend to use Snoop to poke around the visual tree when I need to understand where bound values are coming from. You can use it to look at your control properties and follow either bindings back to DataContexts or walk up and down the tree to see where a value might be inherited from.
Fire up Snoop, drag the crosshairs over your WPF application (or refresh the process list and select from the dropdown), then position your mouse pointer over your TextBlock control and Ctrl-Shift-LeftClick. Snoop will zoom the visual tree in on your control and allow you to look at or edit your control properties.
There is already an answer showing a managed tool.
Here I'm expanding my comment about debugging the InitializeComponent.
Let's assume a basic xaml as a proof of concept.
<Grid>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Blue" />
</Style>
</Grid.Resources>
<Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" Height="200" Margin="10,25,0,0" VerticalAlignment="Top" Width="199">
<TextBlock Name="analysethis" Text = "why is it blue?" TextWrapping="Wrap" />
</Border>
</Grid>
Intercepting the event of the Foreground Property of the TextBlock is difficult because we can't subclass TextBlock (otherwise the style will be lost)
and we can't override a Freezable. Should I have no other solutions, I would set a breakpoint on the following converter
[ValueConversion(typeof(Color), typeof(String))]
public class DebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Color)) return null; // << BP here
var result = value.ToString();
return result;
}
linking it to the TextBlock with a static resource
<TextBlock Name="analysethis" TextWrapping="Wrap" Margin="0,81,0,75"
Text="{Binding RelativeSource={RelativeSource Self},
Path=Foreground.Color, Converter={StaticResource ColConv} }" >
After starting the Debug, one can see the initial default Foreground assigned to "#FF000000" and the starting condition is in the stack
System.Xaml.dll!System.Xaml.XamlObjectWriter.Logic_DoAssignmentToParentProperty(MS.Internal.Xaml.Context.ObjectWriterContext ctx) + 0xc6 byte
System.Xaml.dll!System.Xaml.XamlObjectWriter.Logic_AssignProvidedValue(MS.Internal.Xaml.Context.ObjectWriterContext ctx) + 0x37 byte
Then there is a second hit on the breakpoint and the source of the actual Foreground color can be finally found in the stack
> PresentationFramework.dll!System.Windows.StyleHelper.DoStyleInvalidations(System.Windows.FrameworkElement fe, System.Windows.FrameworkContentElement fce, System.Windows.Style oldStyle, System.Windows.Style newStyle) + 0xcd byte
> WindowsBase.dll!System.Windows.DependencyObject.UpdateEffectiveValue(System.Windows.EntryIndex entryIndex, System.Windows.DependencyProperty dp, System.Windows.PropertyMetadata metadata, System.Windows.EffectiveValueEntry oldEntry, ref System.Windows.EffectiveValueEntry newEntry, bool coerceWithDeferredReference, bool coerceWithCurrentValue, System.Windows.OperationType operationType) + 0x757 byte
+ newEntry {System.Windows.EffectiveValueEntry} System.Windows.EffectiveValueEntry
+ _value {System.Windows.Style} object {System.Windows.Style}
+ _value {#FF0000FF} object {System.Windows.Media.SolidColorBrush}
+ _targetType {System.Windows.Controls.TextBlock} System.Type {System.RuntimeType}
> PresentationFramework.dll!System.Windows.FrameworkElement.UpdateStyleProperty() + 0x63 byte
Please note, if you are interested, in a further effort the PropertyIndex shown above (linked to the Foreground DependencyProperty) could be traced back to the:
BamlSchemaContext, a Non-Public Member of the xamlReader in the WpfXamlLoader.LoadBaml, that contains the System.Xaml.IXamlLineInfo.LineNumber
Edit
Here is a starting draft of how to automate the stack trace analysis
[ValueConversion(typeof(Color), typeof(String))]
[Serializable]
public class SolidBrushToColorConverter : IValueConverter
{
protected MethodInfo EffectiveValueEntryValueGetMethod
{
get
{
if (effectiveValueEntryValueGetMethod == null)
{
var effectiveValueEntryType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(t => t.Name == "EffectiveValueEntry").FirstOrDefault();
if (effectiveValueEntryType == null)
throw new InvalidOperationException();
var effectiveValueEntryValuePropertyInfo = effectiveValueEntryType.GetProperty("Value", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance);
if (effectiveValueEntryValuePropertyInfo == null)
throw new InvalidOperationException();
effectiveValueEntryValueGetMethod = effectiveValueEntryValuePropertyInfo.GetGetMethod(nonPublic: true);
if (effectiveValueEntryValueGetMethod == null)
throw new InvalidOperationException();
}
return effectiveValueEntryValueGetMethod;
}
}
protected MethodInfo EffectiveValuesGetMethod
{
get
{
if (effectiveValuesGetMethod == null)
{
var dependencyObjectType = typeof(DependencyObject);
var effectiveValuesPropertyInfo = dependencyObjectType.GetProperty("EffectiveValues", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance);
if (effectiveValuesPropertyInfo == null)
throw new InvalidOperationException();
effectiveValuesGetMethod = effectiveValuesPropertyInfo.GetGetMethod(nonPublic: true);
if (effectiveValuesGetMethod == null)
throw new InvalidOperationException();
}
return effectiveValuesGetMethod;
}
}
#region Private fields
private MethodInfo effectiveValueEntryValueGetMethod;
private MethodInfo effectiveValuesGetMethod;
#endregion
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Color)) return null;
var result = value.ToString();
if (result.Equals("#FF0000FF")) {
StackTrace st = new StackTrace();
foreach (StackFrame frame in st.GetFrames()) {
if (frame.GetMethod().Name.Equals( "UpdateEffectiveValue" )) {
foreach (ParameterInfo info in frame.GetMethod().GetParameters()) {
Debug.WriteLine ("parameter name " + info.Name
+ ", type " + info.ParameterType.ToString());
if (info.Name.Equals("newEntry")) {
object newEntry = info.GetRealObject(new StreamingContext()); //SET BreakPoint HERE! (to be continued ...)
}
}
}
}
}
return result;
}
here is the view of the Property found from the VS Debugger
WPF inspector has ability to trace styles:
https://wpfinspector.codeplex.com/, see Style tracing section. Is that what are you looking for?
You could use DependencyPropertyHelper.GetValueSource method.
DependencyPropertyHelper is a static class and here is an example how you can use it:
MainWindow.xaml
<Window x:Class="WpfApplication.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"
Foreground="Red" Loaded="OnWindowLoaded">
<Window.Resources>
<Style x:Key="TextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</Window.Resources>
<StackPanel>
<TextBlock x:Name="textBlock1"/>
<TextBlock x:Name="textBlock2" Style="{StaticResource TextBlockStyle}"/>
<TextBlock x:Name="textBlock3" Foreground="Green"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
SetValueSource(this.textBlock1);
SetValueSource(this.textBlock2);
SetValueSource(this.textBlock3);
}
private static void SetValueSource(TextBlock textBlock)
{
textBlock.Text = DependencyPropertyHelper.GetValueSource(textBlock, TextBlock.ForegroundProperty).BaseValueSource.ToString();
}
}
}
Output
I think you have written a common style for text block in the Resource dictionary
Just try overriding the style by
<TextBlock.Resources>
<Style TargetType="{x:Type TextBlock}">
</Style>
</TextBlock.Resources>
Related
I want to add dynamic items with a datatemplate that contains a TextBlock control, but the text of the TextBlock control will be selected from a XAML ResourceDictionary. The staticresource name will be obtained based on the result of the binding value.
How can I do that?
I'm trying something like this, but doesn't works.
<DataTemplate x:Key="languageItemTemplate">
<ContentControl>
<StackPanel>
<TextBlock Text="{StaticResource {Binding ResourceName}}"></TextBlock>
<TextBlock Text="{DynamicResource {Binding ResourceName}}"></TextBlock>
</StackPanel>
</ContentControl>
</DataTemplate>
UPDATE
Thanks to Tobias, the fist option of his answer works. But I need to instance the converter first to get it work. Which one is the best idea to do that?
In the application_startup method and use it for all the application or in the Window.Resources of the window I use the converter?
Maybe a merge of both and do that on the Application.Resources?
thanks for your answer.
private void Application_Startup(object sender, StartupEventArgs e)
{
LoadConverters();
}
private void LoadConverters()
{
foreach (var t in System.Reflection.Assembly.GetExecutingAssembly().GetTypes())
{
if (t.GetInterfaces().Any(i => i.Name == "IValueConverter"))
{
Resources.Add(t.Name, Activator.CreateInstance(t));
}
}
}
OR
<local:BindingResourceConverter x:Key="ResourceConverter"/>
<DataTemplate x:Key="languageItemTemplate">
<ContentControl>
<StackPanel>
<TextBlock Text="{Binding Name, Converter={StaticResource ResourceConverter }}" />
</StackPanel>
</ContentControl>
</DataTemplate>
If the resource is an application level resource you could simply use a converter to convert from the resource name to the actual object like this:
public class BindingResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string resourceKey = value as string;
if (!String.IsNullOrEmpty(resourceKey))
{
var resource = Application.Current.FindResource(resourceKey);
if (resource != null)
{
return resource;
}
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And use it like this:
<TextBlock Text="{Binding ResourceKey, Converter={StaticResource ResourceConverter}}" />
If the resource is in a local scope, we need a reference to the control to search its resources. You can get the resource name and the control by using an attached property:
public class TextBlockHelper
{
public static readonly DependencyProperty TextResourceKeyProperty =
DependencyProperty.RegisterAttached("TextResourceKey", typeof(string),
typeof(TextBlockHelper), new PropertyMetadata(String.Empty, OnTextResourceKeyChanged));
public static string GetTextResourceKey(DependencyObject obj)
{
return (string)obj.GetValue(TextResourceKeyProperty);
}
public static void SetTextResourceKey(DependencyObject obj, string value)
{
obj.SetValue(TextResourceKeyProperty, value);
}
private static void OnTextResourceKeyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
string resourceKey = e.NewValue as string;
if(d is TextBlock tb)
{
var r = tb.TryFindResource(resourceKey);
if (r != null)
{
tb.Text = r.ToString();
}
}
}
}
And you can use it like this:
<Grid>
<Grid.Resources>
<sys:String x:Key="SomeLocalResource">LocalResource</sys:String>
</Grid.Resources>
<TextBlock h:TextBlockHelper.TextResourceKey="{Binding ResourceKey}" />
</Grid>
I have a strange situation wherein I have a binding with a converter in a WPF window. Occasionally, the content of this window is removed from said window and inserted into a tabbed window like so:
public void AddNewTab(Window wpfWindow, String tabTitle, OnFocusHandler onFocusHandler)
{
//Unhook window contents
object content= wpfWindow.Content;
wpfWindow.Content = null;
//Create a new tab
TabItem newTab = new TabItem();
newTab.Header = title;
newTab.Style = (Style)Resources["CorsairTab"];
//newTab.Foreground = Brushes.White;
newTab.Background = Brushes.Transparent;
newTab.Content = content;
//Add it
TabControl.Items.Add(newTab);
//Tie handler if it exists
if (onFocusHandler != null)
_listOnTabSelectedEventHandlers.Add(onFocusHandler);
else
_listOnTabSelectedEventHandlers.Add(null);
//If this is the first tab, make it the opened one
if(TabControl.Items.Count == 1)
TabControl.SelectedIndex = 0;
}
So that's all well and good, but problems arise when the content-stripped window in question has bindings with converters. I've written a converter that inherits from MarkupExtension to avoid StaticReferences. My converter looks like this
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BoolToVisibilityConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isWorking = (bool)value;
if (isWorking)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
And the XAML that refers to it looks like this:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="1" Visibility="{Binding Path=IsEye, Converter={inf:BoolToVisibilityConverter}}">
<TextBlock Text="Working Eye" VerticalAlignment="Top" Foreground="White" Margin="5,5,0,0"/>
<Button Content="Approve All" Command="{Binding ApproveAllTrades}"
Margin="5,0,0,0" Width="auto" Height="auto" VerticalAlignment="Top" Background="#DCDCDC"/>
</StackPanel>
<views:OrdersWorkingEyeView Loaded="EyeOrders_Loaded" Grid.Column="1" Grid.Row="2" Visibility="{Binding Path=IsEye, Converter={inf:BoolToVisibilityConverter}}"/>
Ignoring for the moment that BoolToVisibility is already a defined thing, when I strip the window of its content and load these two particular controls (one defined by me, the other a stack panel), a break point set at ProvideValue hits twice (once for each control), but Convert is only called once, for my custom control. The result is the custom control has the proper visibility but the stack panel does not. I'm pretty sure that the binding itself is working as the path for the bindings on the two controls is the same, and works for the custom control. I can't figure out what the difference is that is causing the conversion to occur for one but not the other (or, perhaps, why it's not binding properly for the StackPanel). Help?
EDIT
For what it's worth, everything works when I don't strip the content out of the window and put it in a new TabItem. The visibilities update and display fine.
I tried your sample and I made the following changes to make it work
private bool _isEye= true;
public bool IsEye
{
get { return _isEye; }
set { _isEye = value;
NotifyFropertyChanged("IsEye");
}
}
Define resources
<Window.Resources>
<!-- local is your namespace-->
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
update the binding
Visibility="{Binding Path=IsEye,Converter={StaticResource BoolToVisibilityConverter}}"
and it works for both usercontrol and stackpanel.
Hope This might help.
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 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));
}
}
}
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>