wpf TreeView - How to call function on IsExpanded property change - wpf

Is there a way in XAML to call a function when the TreeViewItem property IsExpanded changes?
I believe the not so good alternative would be to loop through all TreeViewItems and do an item.IsExpanded += handler call if I understand things correctly.
Or I could check for clicks on the expander element I guess.
What I'm doing is persisting the expand/collapse state of the tree. Please answer the first question before suggesting alternative ways to persist this just to edify me on properties and xaml.

Building on Joel's answer, you can use EventSetters in the TreeViewItem Style which refer to event handlers in your code-behind:
<TreeView ... >
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" >
<EventSetter Event="TreeViewItem.Expanded" Handler="OnTreeExpanded" />
<EventSetter Event="TreeViewItem.Collapsed" Handler="OnTreeCollapsed" />
</Style>
</TreeView.ItemContainerStyle>
...
Code-behind - normal event handlers:
private void OnTreeExpanded(object sender, RoutedEventArgs e)
{
var tvi = (TreeViewItem)sender;
...
e.Handled = true;
}
private void OnTreeCollapsed(object sender, RoutedEventArgs e)
{
var tvi = (TreeViewItem)sender;
...
e.Handled = true;
}
Note: Make sure you set e.Handled = true in the event handlers, or else you'll get events from all parents of the current TreeViewItem as well..

I would bind the IsExpanded property of the TreeViewItem to my model using something like:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
Then I can run thru the model and get the value for IsExpanded and save it. Additionally, when restoring, simply set the IsExpanded property.
Since you need to call other code when changed, implement IsExpanded like so:
private bool _IsExpanded;
public bool IsExpanded
{
get { return _IsExpanded; }
set
{
if (_IsExpanded == value) return;
_IsExpanded = value;
NotifyPropertyChanged( "IsExpanded" );//or however you need to do it
CallSomeOtherFunc();//this is the code that you need to be called when changed.
}
}

Related

How to remove event handler that is assigned through style

I've created a WPF window. In that I've created style for a textbox which have size information as well as some eventsetters; Then, I've created some textboxes assigning the above style.
Now for one case I need to remove events of that textbox. But I cant do that.
Even though I do the following the event is not detached. It still there.
txt9.PreviewLostKeyboardFocus -= txt9_PreviewLostKeyboardFocus;
This occurs only when the event is attached in the style itself.
If it were within the TextBox control the event detaches fine.
My Code:
XAML:
<Window.Resources>
<ResourceDictionary>
<Style x:Key="txtStyle11" TargetType="TextBox">
<Setter Property="Width" Value="150"/>
<Setter Property="Height" Value="35"/>
<EventSetter Event="PreviewLostKeyboardFocus" Handler="txt9_PreviewLostKeyboardFocus"/>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid MinWidth="50">
<TextBox Style="{StaticResource txtStyle11}" x:Name="txt9"/>
</Grid>
CS:
public MainWindow()
{
txt9.PreviewLostKeyboardFocus -= txt9_PreviewLostKeyboardFocus;
}
Hi we cannot Unsubscribe events whcih are subscribed in EventSetter, because
internally EventSetter class becomes immutable and so the properties of the
EventSetter object cannot be modified. Below is the sample which I tried to
illustrate the same. This sample throws exception when we try to change the
property of setterbase class
public partial class MainWindow : Window
{
KeyboardFocusChangedEventHandler mydelegate, emptydelegate;
EventSetter eventSetter;
public MainWindow()
{
InitializeComponent();
emptydelegate = delegate(object sender, KeyboardFocusChangedEventArgs e)
{
};
mydelegate = delegate(object sender, KeyboardFocusChangedEventArgs e)
{
MessageBox.Show("My Own Event is still Unsubscribed");
};
eventSetter = new EventSetter();
eventSetter.Event = TextBox.PreviewLostKeyboardFocusEvent;
eventSetter.Handler = mydelegate;
Style myStyle = new System.Windows.Style(txt9.GetType());
myStyle.Setters.Add(eventSetter);
txt9.Style = myStyle;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
eventSetter.Handler= emptydelegate;
}
}

Set focus on a textbox control in UserControl in wpf

I have created an UserControl which is loaded in a View (Window) in WPF. In my user control I have put a TextBox. I am unable to set focus on this text box when my view loads. I have tried following but nothing works for me:
FocusManager.FocusedElement="{Binding ElementName=PwdBox}"
I have created a FocusExtension to set focus on control.
Please help.
Another option that you have is to create a bool IsFocusedproperty in your view model. Then you can add a DataTrigger to set the focus when this property is true:
In a Resources section:
<Style x:Key="SelectedTextBoxStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFocused}" Value="True">
<Setter Property="FocusManager.FocusedElement"
Value="{Binding RelativeSource={RelativeSource Self}}" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<TextBox Style="{StaticResource SelectedTextBoxStyle}" ... />
Note that at times, you may need to set it to false first to get it to focus (only when it is already true):
IsFocused = false;
IsFocused = true;
This is similar to Sheridan's answer but does not require focus to be set to the control first. It fires as soon as the control is made visible and is based on the parent grid rather than the textbox itself.
In the 'Resources' section:
<Style x:Key="FocusTextBox" TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=textBoxName, Path=IsVisible}" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=textBoxName}"/>
</DataTrigger>
</Style.Triggers>
</Style>
In my grid definition:
<Grid Style="{StaticResource FocusTextBox}" />
Register the Loaded-Event of your UserControl and set the Focus on your PwdBox by calling Focus() when your UserControl is loaded.
public class MyUserControl : UserControl{
public MyUserControl(){
this.Loaded += Loaded;
}
public void Loaded(object sender, RoutedEventArgs e){
PwdBox.Focus();
// or FocusManager.FocusedElement = PwdBox;
}
}
Keyboard focus will be set when the FocusManager.FocusedElement property is set. Since the property is set when an element is initialized, this is often useful for setting initial focus.
However this is not quite the same thing as setting focus on load. If it is unloaded and reloaded, for example, the keyboard focus will not move the second time. The actual intended purpose of the FocusedElement property is for temporary focus scopes (for example, when a menu is opened, the FocusedElement of the window is kept separate from the keyboard focus because the menu is a separate focus scope -- and keyboard focus returns to the FocusedElement when the menu is closed). If you set FocusedElement on a Window, it will not persist -- since a Window is a focus scope, it will automatically update its FocusedElement whenever you move keyboard focus within it.
To set focus on the Loaded event (without using code-behind), this attached property should work for you:
public static class FocusExtensions {
public static readonly DependencyProperty LoadedFocusedElementProperty =
DependencyProperty.RegisterAttached("LoadedFocusedElement", typeof(IInputElement), typeof(FocusExtension),
new PropertyMetadata(OnLoadedFocusedElementChanged));
public static IInputElement GetLoadedFocusedElement(DependencyObject element) {
return (IInputElement)element.GetValue(LoadedFocusedElementProperty);
}
public static void SetLoadedFocusedElement(DependencyObject element, bool value) {
element.SetValue(LoadedFocusedElementProperty, value);
}
private static void OnLoadedFocusedElementChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
var element = (FrameworkElement)obj;
var oldFocusedElement = (IInputElement)e.OldValue;
if (oldFocusedElement != null) {
element.Loaded -= LoadedFocusedElement_Loaded;
}
var newFocusedElement = (IInputElement)e.NewValue;
if (newFocusedElement != null) {
element.Loaded += LoadedFocusedElement_Loaded;
}
}
private static void LoadedFocusedElement_Loaded(object sender, RoutedEventArgs e) {
var element = (FrameworkElement)sender;
var focusedElement = GetLoadedFocusedElement(element);
focusedElement.Focus();
}
}
The usage is the same as FocusManager.FocusedElement, i.e.:
local:FocusExtensions.LoadedFocusedElement="{Binding ElementName=PwdBox}"
It worked for me to simply add this attribute to the opening UserControl tag in my XAML:
FocusManager.FocusedElement="{Binding ElementName=DisplayName, Mode=OneTime}"
Where DisplayName is the name of the textbox I want to receive focus.
What i use in my authentication manager:
private void SelectLogicalControl()
{
if (string.IsNullOrEmpty(TextboxUsername.Text))
TextboxUsername.Focus();
else
{
TextboxPassword.SelectAll();
TextboxPassword.Focus();
}
}
If no username is set, focus on the username-textbox; otherwise the (select all) passwordbox. This is in the codebehind-file, so not viewmodel ;)
On load event set the keyboard focus :
Keyboard.Focus(control);

Expand/collapse groups in treeview by clicking the text

How do I do so that it is possible to expand/collaps groups in the TreeView simply by clicking on the text, instead of clicking the arrow to the left.
You should create style for your Tree Item with next setter:
<Style x:Key="TreeItemStyle"
TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding Path=IsExpanded, Mode=TwoWay}"/>
</Style>
Then add to you group view data class observable property named IsExpanded:
private bool _isExpanded;
public bool IsExpanded
{
get
{
return this._isExpanded;
}
set
{
if (this._isExpanded != value)
{
this._isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
}
}
Then intercept hyper link click event and set IsExpanded as true:
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
var dc = ((Hyperlink)sender).DataContext;
if (dc is GroupViewData)
{
((GroupViewData)dc).IsExpanded = true;
}
}
Of course, the best way is to use commands instead of click handlers, but I don't know composition of your presentation model so can't provide proper solution. I just must say that in our projects with alike requirements we successfully avoid any view code behind. God bless WPF!

Disable Save button in WPF if validation fails

I've adopted what appears to be the standard way of validating textboxes in WPF using the IDataErrorInfo interface and styles as shown below. However, how can I disable the Save button when the page becomes invalid? Is this done somehow through triggers?
Default Public ReadOnly Property Item(ByVal propertyName As String) As String Implements IDataErrorInfo.Item
Get
Dim valid As Boolean = True
If propertyName = "IncidentCategory" Then
valid = True
If Len(IncidentCategory) = 0 Then
valid = False
End If
If Not valid Then
Return "Incident category is required"
End If
End If
Return Nothing
End Get
End Property
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="3" />
<Setter Property="Height" Value="23" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
A couple of things:
First, I would recommend using the RoutedCommand ApplicationCommands.Save for implementing the handling of the save button.
If you haven't checked out the WPF Command model, you can get the scoop here.
<Button Content="Save" Command="Save">
Now, to implement the functionality, you can add a command binding to the Window/UserControl or to the Button itself:
<Button.CommandBindings>
<CommandBinding Command="Save"
Executed="Save_Executed" CanExecute="Save_CanExecute"/>
</Button.CommandBindings>
</Button>
Implement these in code behind:
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
private void Save_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
}
In Save_CanExecute, set e.CanExecute based on the validity of the binding on the text box.
If you want to implement using the MVVM (Model-View-ViewModel) design pattern, check out Josh Smith's post on CommandSinkBinding.
One final note: If you want the enable/disable to be updated as soon as the value in the TextBox is changed, set UpdateSourceTrigger="PropertyChanged" on the binding for the TextBox.
EDIT: If you want to validate/invalidate based on all of the bindings in the control, here are a few suggestions.
1) You are already implementing IDataErrorInfo. Try implementing the IDataErrorInfo.Error property such that it returns the string that is invalid for all of the properties that you are binding to. This will only work if your whole control is binding to a single data object. Set e.CanExecute = string.IsNullOrEmpty(data.Error);
2) Use reflection to get all of the public static DependencyProperties on the relevant controls. Then call BindingOperations.GetBindingExpression(relevantControl, DependencyProperty) in a loop on each property so you can test the validation.
3) In the constructor, manually create a collection of all bound properties on nested controls. In CanExecute, iterate through this collection and validate each DependencyObject/DepencyProperty combination by using BindingOperation.GetBindingExpression() to get expressions and then examining BindingExpression.HasError.
I've created attached property just for this:
public static class DataErrorInfoHelper
{
public static object GetDataErrorInfo(ButtonBase obj)
{
return (object)obj.GetValue(DataErrorInfoProperty);
}
public static void SetDataErrorInfo(ButtonBase obj, object value)
{
obj.SetValue(DataErrorInfoProperty, value);
}
// Using a DependencyProperty as the backing store for DataErrorInfo. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataErrorInfoProperty =
DependencyProperty.RegisterAttached("DataErrorInfo", typeof(object), typeof(DataErrorInfoHelper), new PropertyMetadata(null, OnDataErrorInfoChanged));
private static void OnDataErrorInfoChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var button = d as ButtonBase;
if (button.Tag == null)
button.Tag = new DataErrorInfoContext { Button = button };
var context = button.Tag as DataErrorInfoContext;
if(e.OldValue != null)
{
PropertyChangedEventManager.RemoveHandler(((INotifyPropertyChanged)e.OldValue), context.Handler, string.Empty);
}
var inotify = e.NewValue as INotifyPropertyChanged;
if (inotify != null)
{
PropertyChangedEventManager.AddHandler(inotify, context.Handler, string.Empty);
context.Handler(inotify, new PropertyChangedEventArgs(string.Empty));
}
}
private class DataErrorInfoContext
{
public ButtonBase Button { get; set; }
public void Handler(object sender, PropertyChangedEventArgs e)
{
var dei = sender as IDataErrorInfo;
foreach (var property in dei.GetType().GetProperties())
{
if (!string.IsNullOrEmpty(dei[property.Name]))
{
Button.IsEnabled = false;
return;
}
}
Button.IsEnabled = string.IsNullOrEmpty(dei.Error);
}
}
}
I'm using it like this on my forms:
<TextBlock Margin="2">e-mail:</TextBlock>
<TextBox Margin="2" Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
<!-- other databindings--->
<Button Margin="2" local:DataErrorInfoHelper.DataErrorInfo="{Binding}" Commands="{Binding SaveCommand}">Create account</Button>

Inverse DataTrigger?

I can trigger property settings on my ListBoxItem template based on properties of underlying data object using DataTrigger with something like this
<DataTrigger Binding="{Binding Path=IsMouseOver}" Value="true">
<Setter TargetName="ItemText" Property="TextBlock.TextDecorations" Value="Underline">
</Setter>
</DataTrigger>
But what if I want to do the opposite? I mean set a property value on the underlying data object based on property value of my ListBoxItem. Something like:
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="MyClass.IsHilited" Value="True"></Setter>
</Trigger>
Is there a mechanism for something like this or what would be the recommended approach to deal with situations like this?
Thanks.
I think that you could use an EventSetter to do in XAML what Josh G suggested in code. Maybe create one for the MouseEnter and MouseLeave events, and style the control appropriately for each?
Update: You can set up the events like this:
<ListBox>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseEnter" Handler="OnListBoxItemMouseEnter" />
<EventSetter Event="MouseLeave" Handler="OnListBoxItemMouseLeave" />
</Style>
</ListBox.Resources>
<ListBoxItem>Item 1</ListBoxItem>
<ListBoxItem>Item 2</ListBoxItem>
<ListBoxItem>Item 3</ListBoxItem>
<ListBoxItem>Item 4</ListBoxItem>
<ListBoxItem>Item 5</ListBoxItem>
</ListBox>
The Style registers for the MouseEnter and MouseLeave events for all ListBoxItems defined in that ListBox.
And then in your code behind file:
private void OnListBoxItemMouseEnter(object sender, RoutedEventArgs e)
{
ListBoxItem lvi = sender as ListBoxItem;
if(null != lvi)
{
lvi.Foreground = new SolidColorBrush(Colors.Red);
}
}
private void OnListBoxItemMouseLeave(object sender, RoutedEventArgs e)
{
ListBoxItem lvi = sender as ListBoxItem;
if(null != lvi)
{
lvi.Foreground = new SolidColorBrush(Colors.Black);
}
}
These handlers set the color of the text of the ListBoxItem to red when the mouse is over the item, and back to black when the mouse leaves it.
WPF triggers are intended for causing visual changes. Setter objects within triggers cause property changes on the control.
If you want to respond to an event (like an EventTrigger), you could always simply subscribe to the event in code and then set the data property in the handler.
You could use MouseEnter and MouseLeave in this way. For example:
listBox.MouseEnter += listBox_MouseEnter;
listBox.MouseLeave += listBox_MouseLeave;
void listBox_MouseEnter(object sender, MouseEventArgs e)
{
listBox.MyClass.IsHilited = true;
}
void listBox_MouseLeave(object sender, MouseEventArgs e)
{
listBox.MyClass.IsHilited = false;
}
Some properties on a control you could bind the property of the data object to, like so:
Binding myBind = new Binding("IsHilited");
myBind.Source = listBox.DataContext;
listBox.SetBinding(listBox.IsEnabled, myBind);
You can't use IsMouseOver in a binding, however.
If you create a custom control you can have even greater flexibility to build a binding like this into the control. You could create a custom depency property and sync it with the data property in the DependencyPropertyChanged handler. You could then set this dependency property with a WPF trigger.
Here's an example:
public static readonly DependencyProperty IsHilitedProperty =
DependencyProperty.Register("IsHilited", typeof(bool), typeof(CustomListBox),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsHilitedChanged)));
public double IsHilited
{
get
{
return (bool)GetValue(IsHilitedProperty);
}
set
{
SetValue(IsHilitedProperty, value);
}
}
private static void OnIsHilitedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
CustomListBox box = obj as CustomListBox;
if (box != null)
box.MyClass.IsHilited = box.IsHilited;
// Or:
// Class myClass = box.DataContext as Class;
// myClass.IsHilited = box.isHilited;
}
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsHilited" Value="True"/>
</Trigger>

Resources