I'm trying to create a custom control - a button - which will have multiple styles applied to it depending on the value of a property within the data context.
What I was thinking is using something similar to:
<Button Style="{Binding Path=ButtonStyleProperty, Converter={StaticResource styleConverter}}" Text="{Binding Path=TextProp}" />
And in code... Implement an IValueConverter which does something similar to the code below in the ConvertTo method:
switch(value as ValueEnums)
{
case ValueEnums.Enum1:
FindResource("Enum1ButtonStyle") as Style;
break;
... and so on.
}
However I'm not entirely sure about how to pull out the style object and even if this is possible at all...
What I am doing in the mean time is handling the DataContextChanged event, then attaching a handler to the PropertyChanged event of the object being bound to the button - then running the switch statement in there.
Its not quite perfect but until I can find a better solution it seems like that is what I'll have to use.
If you want to replace the whole style (rather than just elements of it) then you'll probably be storing those styles in resources. You should be able to do something along the lines of:
<Button>
<Button.Style>
<MultiBinding Converter="{StaticResource StyleConverter}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding Path="MyStyleString"/>
</MultiBinding.Bindings>
</MultiBinding>
</Button.Style>
</Button>
By using a MultiBinding and using Self as the first binding we can then lookup resources in our converter. The converter needs to implement IMultiValueConverter (rather than IValueConverter) and can look something like this:
class StyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetElement = values[0] as FrameworkElement;
string styleName = values[1] as string;
if (styleName == null)
return null;
Style newStyle = (Style)targetElement.TryFindResource(styleName);
if (newStyle == null)
newStyle = (Style)targetElement.TryFindResource("MyDefaultStyleName");
return newStyle;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
It's not something I do very often, but that should work from memory :)
It seems that you need to use DataTrigger class. It allows you to apply different styles to your button based on it's content.
For example following style will change button's background property to red based on value of data context object's property
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path="Some property"}"
Value="some property value">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
For those of us who can't use multi value converter (I'm looking at you SL4 and WP7:), thanks to Steven's answer I found a way using an ordinary value converter.
The only assumption is the style value is contained within the property of the style being set.
So if you're using the MVVM pattern then the style value (such as TextSmall, TextMedium, TextLarge) is assumed to be part of the view model, and all you have to do is pass the converter parameter defining the name of style.
For example, say your view model has property:
public string ProjectNameStyle
{
get { return string.Format("ProjectNameStyle{0}", _displaySize.ToString()); }
}
Application style:
<Application.Resources>
<Style x:Key="ProjectNameStyleSmall" TargetType="TextBlock">
<Setter Property="FontSize" Value="40" />
</Style>
<Style x:Key="ProjectNameStyleMedium" TargetType="TextBlock">
<Setter Property="FontSize" Value="64" />
</Style>
<Style x:Key="ProjectNameStyleLarge" TargetType="TextBlock">
<Setter Property="FontSize" Value="90" />
</Style>
XAML view:
<TextBlock
Text="{Binding Name}"
Style="{Binding ., Mode=OneWay, Converter={cv:StyleConverter}, ConverterParameter=ProjectNameStyle}">
With your StyleConverter class implementing IValueConverter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(Style))
{
throw new InvalidOperationException("The target must be a Style");
}
var styleProperty = parameter as string;
if (value == null || styleProperty == null)
{
return null;
}
string styleValue = value.GetType()
.GetProperty(styleProperty)
.GetValue(value, null)
.ToString();
if (styleValue == null)
{
return null;
}
Style newStyle = (Style)Application.Current.TryFindResource(styleValue);
return newStyle;
}
Note that this is WPF code, as the converter is derived from a MarkupExtension as well as IValueConverter, but it will work in SL4 and WP7 if you use static resource and add a bit more leg work as the TryFindResource method doesn't exist.
Hope that helps someone, and thanks again Steven!
ViewModel
private Style _dynamicStyle = (Style)Application.Current.FindResource("Style1");
public Style DynamicStyle
{
get { return _dynamicStyle; }
set
{
_dynamicStyle = value;
OnPropertyChanged("DynamicStyle");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Implement a property in your ViewModel and then dynamically change style where ever you want like below.
DynamicStyle=(Style)Application.Current.FindResource("Style2");// you can place this code where the action get fired
View
Then set DataContext value and then implement the following code in your view
<Button Style="{Binding DynamicStyle,Mode=TwoWay}"/>
Related
I have a custom user control which is in the main window of my WPF application. Within the window is an ItemsControl. I have created a style so that I can bind to an array of items which is a property of my view-model class. The array holds indexes to the position of the items control. I should add that the custom control is inherits from Shape so it has the Stroke property.
public class ViewModel
{
...
public List<int> Selections
{
get => _selections;
set
{
if (value == _selections) return;
_selections = value;
OnPropertyChanged();
}
}
public HypercombState State
{
get => _state;
set
{
if (value == _state) return;
_state = value;
OnPropertyChanged();
}
}
}
Here is the converter that is responsible for identifying whether the view-model is holding selected indexes or not. If should return true if the item is selected during databinding.
public class ArrayContainsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is not int id || values[1] is not List<int> array) return null;
return array?.Contains(id);
}
...
}
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Value="True">
<Condition.Binding>
<MultiBinding Converter="{StaticResource ArrayContainsConverter}">
<Binding Path="(ItemsControl.AlternationIndex)" RelativeSource="
{RelativeSource AncestorType=ContentPresenter}" />
<Binding Path="DataContext.Selections" />
</MultiBinding>
</Condition.Binding>
</Condition>
...
</MultiDataTrigger.Conditions>
<Setter Property="Stroke" Value="Chartreuse"></Setter>
</MultiDataTrigger>
</Style.Triggers>
Whenever the Selections or State properties change, I would like to update the ItemsControl so that there is a Stroke as a visual cue for the item's selected state when true. I can see the Selections property changing if I use breakpoints but there the converter is not triggering when the list changes.
Try changing the datatype from List to ObservableCollection. There is a difference when using those 2 data types in WPF and usually I recommend using the latter for any trigger updates.
Also try updating the binding modes such as
<Binding Path="DataContext.Selections" />
To
<Binding Path="DataContext.Selections", Mode=“TwoWay” />
The difference between the two is that:
List - Implements IList interface
ObservableCollection - Implements INotifyCollectionChanged
Is there a way to bind underline?? I'm trying to achieve the following:
I have VievModel with a bool property:
public bool HomeButtonUnderline { get; set; } = false;
I would then like to control this property in the following function:
public void Home() {
//CurrentPage = ApplicationPage.Home;
//HomeButtonForeground = new SolidColorBrush(Colors.White);
HomeButtonUnderline = true;
SettingsButtonUnderline = false;
}
I could then utilize this control in XAML:
<Button>
<TextBlock Command="{Binding HomeNavCommand}" Underline="{Binding HomeButtonUnderline}"/>
</Button>
The problem is that there isn't an 'Underline' property, instead it is handled by 'TextDecorations':
<Button>
<TextBlock TextDecorations="Underline">
</Button>
So is there a way to control underline using MVVM or even without it??
You may use a DataTrigger in a Style for the TextBlock:
<Button Command="{Binding HomeNavCommand}">
<TextBlock Text="Home">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding HomeButtonUnderline}" Value="True">
<Setter Property="TextDecorations" Value="Underline"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Button>
Use a Converter to convert your model types (in this case, a bool) to the UI types (in this case, a TextDecoration).
public class UnderlineConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return null;
return System.Convert.ToBoolean(value) ? TextDecorations.Underline : null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
And then use it in your binding (after creating a resource in your Window, UserControl App.xaml or whereever you feel is the right place; for this sample I'm just putting it in the button resources)
<Button Command="{Binding HomeNavCommand}">
<Button.Resources>
<local:UnderlineConverter x:Key="UnderlineConverter" />
</Button.Resources>
<TextBlock TextDecorations="{Binding HomeButtonUnderline, Converter={StaticResource UnderlineConverter}"/>
</Button>
Of course, another way to handle this is for your view model to be the right type for the view:
public TextDecorationCollection HomeButtonUnderline { get; set; }
HomeButtonUnderline is already GUI-orientated, so there's probably some code in your viewmodel that assigns a value from the model, so it might as well also convert from bool to TextDecorations.Underline.
This method keeps all the logic in the same file, unlike a converter, although a converter is more reusable. It also has the advantage of being easily unit-testable (unlike a data trigger).
The software uses a SmartSnowUser object, which contains a SecurityRole object. The client needs SecurityRole to be customizable, so it has a list of enum SecurityTasks, which the clients can add/remove from. Controls should only be visible if their given SecurityTask exists in the current SmartSnowUser's
SecurityRole.
With this setup, I am struggling to get all the functionality I need.
I need the ability to:
change control visibility based on whether CurrentUser.Role contains GivenTask
make control visibility more granular when necessary (e.g. Visibility &= isInEditMode)
meet the previous two requirements without having to create a separate style for each color/task/extra-qualifier combination
Here are the two main approaches I've tried.
Attempt #1:
Wpf User Security Strategy
Current issue: visibility is not being triggered; breakpoint in Convert() method is never hit
Long-term issue: Uses style, so every other custom style will need to be BasedOn this default; also, have to duplicate functionality for editability
Code:
/**Style.xaml**/
<local:TagToVisibilityConverter x:Key="TagToVisibilityConverter"/>
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource TagToVisibilityConverter}">
<Binding Path="MainData.CurrentUser"/>
<Binding RelativeSource="{RelativeSource Mode=Self}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
/**Style.xaml.cs**/
public class TagToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length >= 2 && (values[1] as FrameworkElement).GetValue(SecurityLevel.RequiredTaskProperty) is SecurityTask requiredTask)
{
//If element has a task assigned and user is not logged in, do not show
if (values[0] is SmartSnowUser currentUser && currentUser.Role != null)
{
return currentUser.Role.Tasks.Contains(requiredTask) ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
//If element has no task assigned, default to visible
return Visibility.Visible;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class SecurityLevel
{
public static readonly DependencyProperty RequiredTaskProperty = DependencyProperty.RegisterAttached("RequiredTask", typeof(SecurityTask), typeof(FrameworkElement), new PropertyMetadata(SecurityTask.ControlBasic));
public static void SetRequiredTask(UIElement element, SecurityTask value)
{
element.SetValue(RequiredTaskProperty, value);
}
public static SecurityTask GetRequiredTask(UIElement element)
{
return (SecurityTask)element.GetValue(RequiredTaskProperty);
}
}
/**Implementation in User Control**/
<Button Name="BtnNew" Content="Create New Role" Style="{StaticResource ButtonBlue}" server:SecurityLevel.RequiredTask="{x:Static enums:SecurityTask.EditRoles}" />
Attempt #2:
How to extend instead of overriding WPF Styles
How to add dependency property to FrameworkElement driven classes in WPF?
Attempted to merge these two solutions into one. Set the Tag value to a SecurityTask, then use trigger to set visibility
issue: cannot set visibility at a more granular level without a style (e.g. cannot set 'Visibility' property directly in control); cannot distinguish between visibility/editability
Code:
/**Style.xaml**/
<!--#region Visibility-->
<!-- Default frameworkElement style definition -->
<local:TagToVisibilityConverter x:Key="TagToVisibilityConverter"/>
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource TagToVisibilityConverter}">
<Binding Path="Tag" RelativeSource="{RelativeSource Mode=Self}"/>
<Binding Path="MainData.CurrentUser"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
<!-- Extending default style -->
<Style x:Key="ButtonBasic" TargetType="Button" BasedOn="{StaticResource {x:Type FrameworkElement}}">
<Setter Property="Background" Value="{StaticResource BrushGreyDark}" />
<Setter Property="Foreground" Value="{StaticResource BrushWhite}" />
</Style>
<Style x:Key="ButtonBlue" TargetType="Button" BasedOn="{StaticResource ButtonBasic}">
<Setter Property="Background" Value="{StaticResource BrushBlue}" />
</Style>
/**Style.xaml.cs**/
public class TagToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length >= 2 && values[0] is SecurityTask requiredTask)
{
//If element has a task assigned and user is not logged in, do not show
if (values[1] is SmartSnowUser currentUser && currentUser.Role != null)
{
return currentUser.Role.Tasks.Contains(requiredTask) ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
//If element has no task assigned, default to visible
return Visibility.Visible;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
/**Implementation in User Control**/
//This button works great. Exactly what I need.
<Button Name="BtnNew" Content="Create New Role" Style="{StaticResource ButtonBlue}" Tag="{x:Static enums:SecurityTask.EditRoles}" />
//This button does not work, because the newly set Visibility property overrides the style.
<Button Name="BtnEdit" Content="Edit Role" Style="{StaticResource ButtonBlue}" Tag="{x:Static enums:SecurityTask.EditRoles}" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBoolToVisibilityConverter}}" />
Attempt #2 ALMOST works. It's that last stinking button, BtnEdit. It is far too cluttered to create a new style - BasedOn ButtonBlue, which is BasedOn ButtonDefault, which is BasedOn our original up there - every time I need to add another qualifier to my visibility setting.
I seem to be over-complicating this. Is there a cleaner approach to what I'm trying to do?
I'm using a XamDataGrid (Infragistics-control) to display some hierarchical data. The objects that I can have up to 10 levels and I need to be able to give each level a specific background-color. I use the AssigningFieldLayoutToItem-event to get the "level" of the item and it would be best to assign the background/style here as well, I suppose.
I have tried specifying a DataRecordCellArea-style and even a CellValuePresenter-style but I can't get any of these to work with the FieldLayouts.
Another solution is to write a FieldLayout for each level, but this would create a lot of unnecessary XAML-code.
Any suggestions as to what I should do?
If you have a different FieldLayout for each level, you could use a single style targeting the DataRecordPresenter with a converter to set the background.
XAML:
<local:BackgroundConverter x:Key="BackgroundConverter"/>
<Style TargetType="{x:Type igDP:DataRecordPresenter}">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=FieldLayout.Key, Converter={StaticResource BackgroundConverter}}"/>
</Style>
Converter:
public class BackgroundConverter:IValueConverter
{
public BackgroundConverter()
{
this.Brushes = new Dictionary<string, Brush>();
}
public Dictionary<string, Brush> Brushes {get;set;}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string)
{
string key = value.ToString();
if (this.Brushes.ContainsKey(key))
return this.Brushes[value.ToString()];
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The following will set the colors to use for fields with Key1 and Key2:
BackgroundConverter backgroundConverter = this.Resources["BackgroundConverter"] as BackgroundConverter;
backgroundConverter.Brushes.Add("Key1", Brushes.Green);
backgroundConverter.Brushes.Add("Key2", Brushes.Yellow);
If you are reusing the same FieldLayout for multiple fields, then you could use the InitializeRecord event and change the style to bind to the Tag of the DataRecord like this:
XAML:
<Style TargetType="{x:Type igDP:DataRecordPresenter}">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Record.Tag}"/>
</Style>
C#:
void XamDataGrid1_InitializeRecord(object sender, Infragistics.Windows.DataPresenter.Events.InitializeRecordEventArgs e)
{
if (!e.ReInitialize)
{
// Set the tag to the desired brush.
e.Record.Tag = Brushes.Blue;
}
}
Note that I didn't add the conditional logic for determining the brush to use and that still needs to be done for different levels to have different backgrounds.
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>