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.)
Related
I've been trying to just hide items from a TreeView. I'm using a custom data type as source (called SettingsMenuItem) which inherits from FrameworkElement (currently FrameworkContentElement, because otherwise the TreeView renders them wrong).
My goal is by setting the VisibilityProperty of these FrameworkElements to either Collapsed or Visible that I'm able to hide certain items (including their children). I know that this can be done by deleting items from the source collection. But that's not what I want. It would mean that I have to mirror each collection in order to keep track of it's actual items, bind to each one in order to be notified about Visibility-changes and create a new collection each time one changes. A lot of overhead for this.
Right now I have no clue how I could accomplish that. I figure it's related to the ItemsGenerator, but I haven't seen any possibility to override it's behaviour. I thought TreeView would be able to detect Visibility, but obviously it doesn't. As alternative I thought of a custom TreeViewItem (maybe even TreeView if necessary) - but at this point the abstraction of this whole system overwhelms me. I don't know where to start and what is actually necessary to solve the problem.
Tips what I have to change or implement by myself would be more than enough. A complete solution would be nice.
You can do this using a data trigger bound to a property (e.g. "IsVisible") in you tree data nodes:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
While this technically answers your question I would be wary of actually doing it. User3690202's comment is correct, it's the sort of thing you would normally do via filtering in your view model.
For alternate solution using code behind xaml.cs:
To Remove a specific TreeViewItem from a TreeView which is created from a code behind.
TreeViewItem treeViewItem1 = new TreeViewItem
{
Visibility = Visibility.Collapsed,
};
use the code with TreeViewItem you want to hide in a if condition to hide specific TreeViewItem Header let say "Cars" and you want to hide it and use the code with if condition to hide "Cars" TreeViewItem.
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.
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.
I'm writing an application in WPF using the MVVM-pattern and will really often use TextBoxes.
I don't want to use labels for the user to know user what the text box is for, i.e. I don't want something like this:
<TextBlock> Name: </TextBlock>
<TextBox />
Instead, I would like the TextBox to contain its own label. Statically, you would express it like this:
<TextBox>Name</TextBox>
If the cursor is displayed in the textbox, i.e. the TextBox gains focus, I want the description text to disappear. If the TextBox is left empty and it loses the focus, the description text should be shown again. It's similar to the search textbox of StackOverflow or the one of Firefox. (please tell me if your not sure what I mean).
One TextBox's label may change at runtime, dependending on e.g. a ComboBox's selected element or a value in my ViewModel. (It's like in Firefox's search TextBox, if you select google from the search engins' menu, the TextBox's label changes to "Google", if you select "Yahoo" its set to "Yahoo"). Thus I want to be able to bind the label's content.
Consider that I may already have a Binding on the Text-Property of the TextBox.
How can implement such a behaviour and make it reusable for any of my TextBox's? Code is welcome but not needed; a description of what to do is enough.
Thank you in advance.
Here is a style I think is exactly what you are looking for, and it's pure XAML.
<Style x:Key="WatermarkTextBox" TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Border x:Name="BorderBase" Background="White" BorderThickness="1.4,1.4,1,1" BorderBrush="Silver">
<Label x:Name="TextPrompt"
Content="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Tag}"
Background="{TemplateBinding Background}" Visibility="Collapsed"
Focusable="False" Foreground="Silver"/>
</Border>
<ScrollViewer Margin="0" x:Name="PART_ContentHost" Foreground="Black"/>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="False"/>
<Condition Property="Text" Value=""/>
</MultiTrigger.Conditions>
<Setter Property="Visibility" TargetName="TextPrompt" Value="Visible"/>
</MultiTrigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="BorderBrush" TargetName="BorderBase" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="DimGray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage is:
<TextBox Style="{StaticResource WatermarkTextBox}" Tag="Full Name"/>
where Tag is the help message you want to show.
You could clean up this style for your own use, but the most important part is the which controls hiding/showing the helper text.
It's worth noting as well, there is already a DependencyObject available for storing the helper text, so you don't need to create your own with this method.
FrameworkElement.Tag is available for holding arbitrary information about this element. That's why we set the Tag property:
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.tag.aspx
You could derive from TextBox and implement your behaviour. The TextBox offers the events GotFocus/LostFocus (or the methods OnGotFocus/OnLostFocus respectively) which should help. You also should consider offering a new DepedencyProperty, so you can define the default text in xaml and bind it to other controls/resources etc.
To amplify on my suggestion about using an adorner.
An Adorner is basically an element, rendered on its own layer, that appears over/around another element. For instance, if you implement validation in a binding, the red box that decorates an invalid control is an adorner - it's not part of the control, and it can be (and is) applied to all kinds of controls. See the Adorners section of the WPF docs for a simple but clear example.
I thought of an Adorner for a couple of reasons. The principal one is that the behavior you're describing might not necessarily be confined to a TextBox. You might, for instance, want to have a ComboBox exhibit the same behavior. Implementing an Adorner would give you a consistent way to implement this functionality across multiple controls (though it doesn't make sense in, say, a CheckBox or a ProgressBar). A second is that you wouldn't have to do anything to the underlying control more elaborate than implementing triggers to display and hide the Adorner in response to focus events. Adorners are a bit of a pain in the butt to implement, but it's worth knowing how to.
All that said, I like mattjf's answer a lot more than I like mine. The only disadvantages I see with that approach are 1) It only works with the TextBox; you need to implemnent a new version of the style every time you want to use the approach on another control, 2) I may just be engaging in magical thinking, but every time I ever used the Tag property in WinForms it told me (once I learned to listen) that I was building something fragile. I don't know for sure that this is also true in WPF, but I bet it is.
My comment on using the bound Text property probably needs amplification. If you use the Text property to store the field label, then you've got a number of hard-to-solve problems. First, since it's a bound property, changing its value in the TextBox will change it in the source. So now your source needs to know a lot of information about the state of the UI - does the control currently have the focus? If the value of the Text property is Foo, does that mean that the label is Foo, or the user typed in Foo? There are probably ways that you can manage this, but the best way to manage it is to not have to.
(One other problem with this paradigm: What should be the behavior be if the user wants the value of the TextBox to be the empty string?)
I have a ListBox that when in focus, and when I have an item selected returns a valid SelectedIndex. If I have a valid SelectedIndex and I click on a TextBox on the same Forum, the SelectedIndex now becomes -1. However I want it to keep its SelectedIndex from changing. How would I go about doing this?
ListBox will keep it's SelectedIndex regardless of focus.
I tested it on a blank project with one ListBox, one TextBox and one Label used to display the ListBox's SelectedIndex. Under both the ListBox's SelectedIndexChanged and the TextBox's TextChanged events I updated the Label with the ListBox's SelectedIndex
There must be something else going on to cause the Selected Index to change to -1.
I had the same issue as original poster.
I couldn't figure it out totally but it seems like when you have the listbox bound to an observable collection and the collection gets changed that the selected item loses the focus.
I hacked around the issue by saving the selected index in a variable and resetting it if the selected index was -1 (and it was valid to restore it)
This is an old question, but in case someone else experiences the same problem check your ListBoxItem style especially if you are using one of styles from WPF Themes.
The problem with WPF Themes specifically is the inclusion of the
section outside of the Control Template:
<Style d:IsControlPart="True" TargetType="{x:Type ListBoxItem}">
....
<Style.Triggers>
<Trigger Property="Selector.IsSelected" Value="True">
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush po:Freeze="True" Color="{DynamicResource BlackColor}" />
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="IsSelected" Value="true" />
</Trigger>
</Style.Triggers>
</Style>
Delete the Style.Triggers and the problem should go away
Handle the SelectedIndexChanged event and save the selected value so that you can restore it when your control regains focus.
I haven't verified this in my apps but if the SelectedIndex property changes when the LB loses focus you probably have to handle that case yourself by caching the last selected index and resetting it when the control regains focus. You can do this in the containing form or you can do it in a class derived from ListBox.
You could even try setting the selected index as soon as you see it becomes -1. Not sure what would happen but I'd be curious to find out....
Edit: just tested it and like the other poster I can't reproduce it either. Must be something slightly different about your LB
Are these controls in different dialogs, or maybe different tabs on a tabbed container? That's the only way I can think of that you would lose your SelectedIndex when changing focus. Otherwise, how would anybody e.g. click a button to take action on an item? You'd lose the selection when focus went to the button you're clicking...