When I have a control A that contains a control B, there are properties Prop that are inherited. That means, B.Prop will automatically take the value of A.Prop if B.Prop is not explicitly set. As far as I know, IsEnabled is such a property.
Now I have a situation where I do set the value of B.IsEnabled explicitly, and still it is overwritten by the value of A.IsEnabled. Why is that so, and how can I correct it?
In this situation A is a StackPanel and B a TextBox:
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding InDisableMode}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBox Text="some text">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding InDisableMode}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
The above XAML snippet has its DataContext set to my ViewModel. The ViewModel contains a property InDisableMode which is a bool. When it is false, everything is as expected: The StackPanel is enabled and the TextBox is disabled.
But when InDisableMode is true, both the StackPanel and the TextBox are disabled although both Triggers should trigger!
Note: I know I can databind IsEnabled to InDisableMode in both controls (in the TextBox directly, in the StackPanel by using a complement converter). I have not tried if this works since I want to do this with Triggers anyway.
EDIT:
The point of disabling the StackPanel is to disable all of its children easily (except the TextBox which I want to enable instead). Any other ideas how to solve this task without changing the parent-child-relationship or creating new controls? At the moment, the only way I see is to disable all children except the TextBox one by one...
If you disable a control, its children are disabled. Since a StackPanel is not interactive, there is no reason to disable it other than to disable its interactive children.
If you want to enable a control A while its parent B is disabled, you can't do that. B cannot be the parent if you want to enable A while B is disabled.
For a workaround, you can put them both in a Grid, with the TextBox defined last, to superimpose the TextBox on top of the StackPanel. Then it will be within the StackPanel's area but it won't be a child of the StackPanel.
This happens because UIElement.IsEnabled property uses value coercion by inheriting the value from its parent. This it does by using CoerceValueCallback. Value coercion is ranked first in Dependency Property Setting Precedence List.
So, to override this behavior, we have two options. Firstly, to use AddOwner() to register our type as new owner of IsEnabled property. Secondly, to override the metadata using OverrideMetadata(). This second method would work only if you inherit directly from UIElement.
So, lets say we want our Button to behave differently, we should create a new Button like below :
public class CButton : Button
{
public static readonly DependencyProperty IsEnabled;
static CButton()
{
IsEnabled = UIElement.IsEnabledProperty.AddOwner(typeof(CButton),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.None,
UIElement.IsEnabledProperty.DefaultMetadata.PropertyChangedCallback,
new CoerceValueCallback(IsEnabledCoerceCallback)));
}
private static object IsEnabledCoerceCallback(DependencyObject d, object baseValue)
{
return (bool) baseValue;
}
}
Here, we are returning assigned value as it is from IsEnabledCoerceCallback. Before returning, you can also introduce the behavior : If user doesn't provide any value for IsEnabled, then use inherited value from parent, else use CButton.IsEnabled user assigned value.
On a side note, try setting null in place of new CoerceValueCallback(IsEnabledCoerceCallback) , see what happens.
Related
I have a non-editable combobox (SupplierDropdown), and I would like when the user chooses the last value on the list, the combobox should become editable, with no value and automatically focused ready for the user to type their value. In other words, an editable, blank box with a blinking cursor ready for input.
Here's the code I have:
private void SupplierDropdown_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (SupplierDropdown.SelectedIndex == SupplierDropdown.Items.Count - 1)
{
SupplierDropdown.IsEditable = true;
SupplierDropdown.Text = "";
SupplierDropdown.Focus();
}
}
However, although the combobox indeed becomes editable, the rest of the code doesn't seem to work: (a) the combobox doesn't clear the value the user chose, and (b) the combobox doesn't get the focus, the user needs to hit tab once to see the blinking cursor in the combobox.
How can I make this work?
This seems like a major pain, because the control just is not intended to be used that way. Even if you get the initial logic to work, it is difficult to turn the editable flag back off in a sound way. When the user enters text that partially matches a given option it will select said option. How do you know whether that selection was caused by accident or intentionally and thus whether the editable flag should be set to false again?
Would recommend the additional TextBox scheme, where you just show a TextBox for any custom values, can be done in XAML only (if you know what value to trigger on) but that's not necessary.
<ComboBox Name="cb" SelectedValuePath="Content">
<ComboBoxItem>A</ComboBoxItem>
<ComboBoxItem>B</ComboBoxItem>
<ComboBoxItem>Other</ComboBoxItem>
</ComboBox>
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedValue, ElementName=cb}" Value="Other">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
(PS: This is an example of using the item/value instead of the index, even if someone decides that the "other" option should be at the top instead, the code will still work as intended, yours would not.)
I have a WPF form with multiple controls, and I want a button ('Assign') to be enabled if and only if a variety of conditions on those controls are true. Some conditions include testing whether textboxes are empty.
I initially achieved this by binding the contents of the textboxes to properties and binding the IsEnabled property of the button in the XAML:
<TextBox Name="NewName" Text="{Binding NewName}" />
(etc)
<Button Name="Assign" ... IsEnabled="{Binding Path=AssignEnabled}" />
with a corresponding method in the C# ViewModel:
public bool AssignEnabled
{
get
{
return !string.IsNullOrWhiteSpace(this.NewName) && ... (etc)
}
set
{
...
this.NotifyPropertyChanged("AssignEnabled");
...
}
}
The problem was that this caused the button to be updated when the focus was lost from the respective textbox, not whenever the text was changed. I could have used the TextChanged property on each textbox in the XAML to call code, but this seemed overcomplicated.
To fix this, I removed the binding and switched to DataTriggers like this:
<Button Name="Assign" ... >
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, ElementName=NewNameOption}" Value="true" />
<Condition Binding="{Binding Text, ElementName=NewName}" Value="{x:Static sys:String.Empty}" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False"/>
</MultiDataTrigger>
...
(more conditions here)
...
</Style.Triggers>
</Style>
</Button.Style>
</Button>
This worked absolutely fine. The only drawback was that the unit tests I had previously written to test the IsEnabled status of the button were no longer usable (since they used the AssignEnabled property of the ViewModel which is no longer bound to the button).
To allow those unit tests to work, I needed the AssignEnabled property to reflect the IsEnabled status of the button. I expected to be able to fix this by adding a OneWayToSource binding, like this:
<Button Name="Assign" ... IsEnabled="{Binding AssignEnabled, Mode=OneWayToSource}">
with the property changed to:
public bool AssignEnabled { get; set; }
However, it seems as though this binding, even though set to OneWayToSource, overrides the DataTriggers, since the enabling/disabling of the button no longer works at all.
I know there are other options, such as using a Converter or implementing ICommand, but I would prefer to keep this simple if possible and fix the above method (or at least understand why it doesn't work). How can I access the IsEnabled property of the button without violating the ViewModel paradigm or preventing the DataTriggers from working?
You can keep your first solution and set UpdateSourceTrigger=PropertyChanged for the bindings. In this way Bindings will change instantly when text changes and not only when focus is lost. By the way this solution gives you mor flexibility as you can perform more complex testing on your fields (for exemple test email adress format).
I am fairly new to WPF but have spent time researching WPF validation, and have not yet seen a good approach to conditional validation.
To simplify the situation greatly, let's say I have two textboxes and a submit button. The user enters a string in the first textbox. If the user enters, for example "ABC", then the second textbox should be a required field (I'd want the background to be a light blue color, to signify this), and the submit button should be disabled until that textbox is populated.
How can this be done? Is there an easy way to add/remove validations in runtime? 'DataAnnotations' (http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx) seemed like a good starting place, however I can't mark a field with the [Required] attribute, as the field won't always be required. Basically, I need something like 'Required if Field1 = 'ABC'
Thanks!
I would handle it using MVVM and here is a sample for that.
Implement IDataError Info on the class and that will implement two properties Error and this[string columnName] you can implement the second property with your binding errors that you want
public class MainViewModel:ViewModelBase,IDataErrorInfo
{
public string Error
{
}
public string this[string columnName]
{
get
{
string msg=nulll;
switch(columnName)
{
case "MyProperty": //that will be your binding property
//choose your validation logic
if(MyProperty==0||MyProperty==null)
msg="My Property is required";
break;
}
return msg;
}
}
Also Set ValidateOnErrors=True in binding of a textbox. here ColumnName is the name of the property that is changed and that has ValidateOnErrors set to true. Check here and put up the conditions and return message then you will see the errors on the tooltip when you put this style in your Resources.
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true" >
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Background" Value="MistyRose"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="1.0"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
and here is a sample of the textbox
<TextBox Text="{Binding UpdateSourceTrigger=PropertyChanged, Mode=TwoWay,
Path=PropertyName,ValidatesOnDataErrors=True}" Name="textBox1">
<Validation.ErrorTemplate>
<ControlTemplate>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
I would just handle this logic in your ViewModel (assuming you're using an MVVM pattern, if not just in your code-behind).
Fire some logic on the TextChanged event for the first textbox that ultimately sets the appropriate properties. Essentially I'm saying code this validation manually. Once you start getting into more complex validation logic like this your going to start running into the limitations of the validation frameworks / declarative validation.
So, I have this Window with some controls. In the resources section I've defined this style:
<Style x:Key="StyleNavBar" TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentTheme, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<DataTrigger.Value>
<theme:WinTheme>WindowsClassic</theme:WinTheme>
</DataTrigger.Value>
<Setter Property="Background" Value="#FFFFFFFF" />
</DataTrigger>
</Style.Triggers>
</Style>
In my Window I define an attached property named 'CurrentTheme' which stores (based on an enum) the current theme used system-wide. Here's the code:
public static readonly DependencyProperty CurrentSystemThemeProperty =
DependencyProperty.RegisterAttached(
"CurrentSystemTheme",
typeof(WinTheme),
typeof(MainWindow),
new UIPropertyMetadata(WinTheme.AeroGlass));
public WinTheme CurrentTheme
{
get
{
return (WinTheme)GetValue(CurrentSystemThemeProperty);
}
set
{
SetValue(CurrentSystemThemeProperty, value);
}
}
Everytime the user changes the system theme, my window receives a callback via WndProc, informing that the theme has changed. And, as you can see, the default value of the CurrentTheme property is WinTheme.AeroGlass. Then I have a grid styled with the style defined above:
<Grid Height="34" Name="grdNavBar" VerticalAlignment="Top" Style="{DynamicResource StyleNavBar}">
My goal is to change the style based on the value of CurrentTheme, but the trigger defined above does not work when the theme changes (it only works with the default value of CurrentTheme, i.e., isn't reacting to changes in the property).
Any ideas on how to accomplish this?
You've passed a different name to DependencyProperty.RegisterAttached than the name you've given the property in C#. So the DP system thinks it's called CurrentSystemTheme, but your code thinks it's called CurrentTheme. Try passing CurrentTheme as the first argument to RegisterAttached.
Also, you might want to enable WPF debug log output for data binding (which is on by default in older versions of WPF, but in .NET 4/VS 2010, you need to go and switch it on in the Tools->Options window under Debugging->Output Window). That way, I usually set the WPF Trace Settings -> Data Binding option to All. That way if a data binding fails, you'll see an error in the Output window. This might help you diagnose why that data trigger is failing.
I've hit a bit of a dead end in trying to figure this one out... Using the MVVM pattern in WPF, our C# Model fires an event to say something has happened. I want to be able handle that event in my ViewModel and then either kick of a storyboard or change the visibility of a hidden panel on the current Xaml Page. This has to be handled with no Code Behind.
I can sync for the event in my ViewModel, update a property to say what the name of that event is and fire a NotifyPropertyChanged even but how do I get that to either kick off a storyboard or map to a boolean true/false on the Visibility property of my Grid? The property I bind to hs to be the event name as different grids may be shown based on different events so I need a way of mapping this to a boolean. However the ideal solution would be to kick off a storyboard. I've looked at DataTriggers but they all seem to be linked to styles and not to actual pages.
Any ideas of how I can achieve this?
Thanks!
I've used this in the past to kick off a storyboard in code-behind
Storyboard animation = (Storyboard)this.FindResource("ShowPanelStoryboard");
animation.Begin();
This code goes behind the View, not in the ViewModel. Personally, I don't mind some code behind my View providing it is only related the View. In the project I used this in, I added a listener to the VisibilityChanged event and when it got changed to Visible, I ran the storyboard.
As for showing your popup, there's a few ways. One of my favorites was just adding an IsPopupShown property to the ViewModel, binding my panel's visibility to it, and setting it to true anytime the popup should be shown. The ViewModel then handles the events that trigger the popup being shown or not.
An alternative as suggested by Dave White is to use a converter. If your value is not always true/false then you could create a converter that checks if a bound value is equal to the ConverterParameter, and return a Visibility value.
From your comment, it seems to me like what you may want to do is expose an Event property of type object in your view model. When the view model receives an event, it sets Event to an object of a type appropriate for that event. In your XAML, you have this:
<ContentControl Content="{Binding Event}"/>
and in the resource dictionary define a DataTemplate for each specific type of event you want to display. If Event is null, nothing gets displayed. If Event contains an object that you've defined a DataTemplate for, it gets displayed using that template.
Yes, you'll need to create a class for each type of event (if you don't already have one).
Another way is to implement the poor man's template selector:
<TextBlock Text="This is displayed if Foo contains 'BAR'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAR">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="This is displayed if Foo contains 'BAZ'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAZ">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
It's kind of stupidly verbose, but it's an easy way to handling a lot of mutually-exclusive display options.
Bind the Visibility property on your grid, in Xaml, to the boolean property on your ViewModel.
<Grid Visibility="{Binding Path=VisiblePropertyOnViewModel}">
Now do whatever you need in your ViewModel and set the property. As long as it does INotifyPropertyChanged or is a DependencyProperty, it will work.
I'd have to do more digging to figure out how to kick off a Storyboard, but I have no doubt it would be almost as easy. Storyboards can be kicked off by PropertyTriggers as well I believe. I'll leave this to get you started.