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!
Related
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);
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.
}
}
I am trying to validate my model class using IDataErrorInfo as given below.
//Validators
public string this[string propertyName] {
get {
string error = null;
if (propertyName == "Name") {
error = ValidateName();
}
return error;
}
}
This works fine, except that when the view first loads it already contains validation errors. Is it possible to ignore/suppress validation errors when the view is first loaded. Also, is it a common practice to show errors when the view loads and before the User starts the data entry for the model properties.
regards,
Nirvan.
Edit:
This is how I am setting up IDataErrorInfo.
<TextBox Text="{Binding Name, ValidatesOnDataErrors=True}" Grid.Row="1" Grid.Column="1" />
I have took the following approach and it works. Basically, the Model should rightfully record errors and have them listed up in a dictionary, even if the object is just instantiated and User hasn't entered any Text yet. So I didn't change my Model code or the IDataErrorInfo validation code. Instead, I just set the Validation.Error Template property to {x:Null} initially. Then there is code to wire up the TextBox's LostFocus event that changes the Validation.Error template back to what I am using. In order to achieve the swapping of templates and attaching LostFocus event handler to all TextBox's in my application, I used a couple of dependency properties. Here is the code that I have used.
Dependency Properties and LostFocus Code:
public static DependencyProperty IsDirtyEnabledProperty = DependencyProperty.RegisterAttached("IsDirtyEnabled",
typeof(bool), typeof(TextBoxExtensions), new PropertyMetadata(false, OnIsDirtyEnabledChanged));
public static bool GetIsDirtyEnabled(TextBox target) {return (bool)target.GetValue(IsDirtyEnabledProperty);}
public static void SetIsDirtyEnabled(TextBox target, bool value) {target.SetValue(IsDirtyEnabledProperty, value);}
public static DependencyProperty ShowErrorTemplateProperty = DependencyProperty.RegisterAttached("ShowErrorTemplate",
typeof(bool), typeof(TextBoxExtensions), new PropertyMetadata(false));
public static bool GetShowErrorTemplate(TextBox target) { return (bool)target.GetValue(ShowErrorTemplateProperty); }
public static void SetShowErrorTemplate(TextBox target, bool value) { target.SetValue(ShowErrorTemplateProperty, value); }
private static void OnIsDirtyEnabledChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) {
TextBox textBox = (TextBox)dependencyObject;
if (textBox != null) {
textBox.LostFocus += (s, e) => {
if ((bool) textBox.GetValue(ShowErrorTemplateProperty) == false) {
textBox.SetValue(ShowErrorTemplateProperty, true);
}
};
}
}
If IsDirtyEnabled dependency property is set to true, it uses the callback to attach the TextBox's LostFocus event to a handler. The handler just changes the ShowErrorTemplate attached property to true, which in turn triggers in textbox's style trigger to show the Validation.Error template, when the TextBox loses focus.
TextBox Styles:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationErrorTemplate}"/>
<Setter Property="gs:TextBoxExtensions.IsDirtyEnabled" Value="True" />
<Style.Triggers>
<Trigger Property="gs:TextBoxExtensions.ShowErrorTemplate" Value="false">
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
</Trigger>
</Style.Triggers>
</Style>
This may seem too much of code for a simple thing, but then I have to do it only once for all the Textboxes I am using.
regards,
Nirvan.
try to set the data context AFTER the view has been shown.
In my case, this helped.
Are you throwing the exception in the get?
public string Name
{
get { return _name; }
set
{
_name = value;
if (String.IsNullOrEmpty(value))
{
throw new ApplicationException("Customer name is mandatory.");
}
}
}
you set the rules in your ValidateName() method. your view just show the error :) i suggest that name is a mandatory field and should be filled by the user but you dont like the red border when the view is first loaded?
i use two different control templates for validation.errortemplate the normal one and one for mandatory fields (a red *)
thats the way i did it the last time.
I have the same command that I want to use for two controls on a dialog type window. As potentially interesting background, I'm using Josh Smith's ViewModel / RelayCommand ideas, since I am new to WPF and it's the first thing I've seen that I can actually understand from a big picture point of view.
So the command is a property of a ViewModel, and with the Button's built-in support, it is trivial and painless to bind to the command in the XAML:
<Button ... Command="{Binding Path=PickCommand}" Content="_Ok"></Button>
Now in a ListView, the only way I have gotten to use the same command hooked up to trigger on a double click is by using an event handler:
<ListView ...
ItemsSource="{Binding Path=AvailableProjects}"
SelectedItem="{Binding Path=SelectedProject, Mode=TwoWay}"
MouseDoubleClick="OnProjectListingMouseDoubleClick"
>
private void OnProjectListingMouseDoubleClick(object sender, MouseButtonEventArgs e) {
var vm = (ProjectSelectionViewModel) DataContext;
vm.Pick(); // execute the pick command
}
Is there a way to do this by binding the way the button does it?
Cheers,
Berryl
<------- implementation - is there a better way? --->
Your SelctionBehavior class was spot on, but I was confused at your xaml code. By setting the "Style" on the listViewItem I was getting the children of the DataContext where the command I want to execute lives. So I attached the behavior to the ListView itself:
<ListView ...Style="{StaticResource _attachedPickCommand}" >
And put the style in a resource dictionary:
<Style x:Key="_attachedPickCommand" TargetType="ListView">
<Setter Property="behaviors:SelectionBehavior.DoubleClickCommand" Value="{Binding Path=PickCommand}" />
</Style>
It works! But it 'feels' awkward setting the style property of the list view. Is this just because I am not comfortable with style as more than something visual in wpf or is there a better way to do this?
Cheers, and thanks!
Berryl
Yes there is! You can use attached behaviors and bind the command to that behavior.
public class SelectionBehavior {
public static readonly DependencyProperty CommandParameterProperty=
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(SelectionBehavior));
public static readonly DependencyProperty DoubleClickCommandProperty=
DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(SelectionBehavior),
new PropertyMetadata(OnDoubleClickAttached));
private static void OnDoubleClickAttached(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var fe=(FrameworkElement)d;
if(e.NewValue!=null && e.OldValue==null) {
fe.PreviewMouseDown+=fe_MouseDown;
} else if(e.NewValue==null && e.OldValue!=null) {
fe.PreviewMouseDown-=fe_MouseDown;
}
}
private static void fe_MouseDown(object sender, MouseButtonEventArgs e) {
if(e.ClickCount==2) {
var dep=(FrameworkElement)sender;
var command=GetDoubleClickCommand(dep);
if(command!=null) {
var param=GetCommandParameter(dep);
command.Execute(param);
}
}
}
public static ICommand GetDoubleClickCommand(FrameworkElement element) {
return (ICommand)element.GetValue(DoubleClickCommandProperty);
}
public static void SetDoubleClickCommand(FrameworkElement element, ICommand value) {
element.SetValue(DoubleClickCommandProperty, value);
}
public static object GetCommandParameter(DependencyObject element) {
return element.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject element, object value) {
element.SetValue(CommandParameterProperty, value);
}
}
and in the xaml you would need to set a style for a ListViewItem which represents your data in the ListView. Example
<ListView>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="local:SelectionBehavior.DoubleClickCommand" Value="{Binding Path=DataContext.PickCommand}"/>
<Setter Property="local:SelectionBehavior.CommandParameter" Value="{Binding Path=DataContext}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Here is some more information about the Attached Behavior pattern
I have a two-level hierarchy displayed in a WPF TreeView, but I only want the child nodes to be selectable - basically the top level nodes are for categorisation but shouldn't be selectable by themselves.
Can I achieve this?
Thanks...
Define styles for each type of items, like Bijington wrote. for non-selectable nodes set the Focusable-Property of the container (TreeViewItem for TreeViews) to false.
To do so you would need to override the style for treeview. Ideally you will have two types of treeview items one for your top-level nodes (im assuming folders) and another simply for the children, then you should be able to define how each item type in the tree behaves.
So create a style for each item type, then for the folder node simply change the trigger for is selected to do nothing.
I've written at attached property that will unselect a treeviewitem as soon as it's selected:
public class TreeViewItemHelper
{
public static bool GetIsSelectable(TreeViewItem obj)
{
return (bool)obj.GetValue(IsSelectableProperty);
}
public static void SetIsSelectable(TreeViewItem obj, bool value)
{
obj.SetValue(IsSelectableProperty, value);
}
public static readonly DependencyProperty IsSelectableProperty =
DependencyProperty.RegisterAttached("IsSelectable", typeof(bool), typeof(TreeViewItemHelper), new UIPropertyMetadata(true, IsSelectablePropertyChangedCallback));
private static void IsSelectablePropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
TreeViewItem i = (TreeViewItem) o;
i.Selected -= OnSelected;
if(!GetIsSelectable(i))
{
i.Selected += OnSelected;
}
}
private static void OnSelected(object sender, RoutedEventArgs args)
{
if(sender==args.Source)
{
TreeViewItem i = (TreeViewItem)sender;
i.IsSelected = false;
}
}
}
Unfortunately you still lose the old selection when you click on an unselectable item :(
You can make only the child nodes selectable by using a trigger in a style, assuming all child nodes have no child nodes, like so:
<TreeView Name="MyTreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<Trigger Property="HasItems" Value="true">
<Setter Property="Focusable" Value="False" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Setting Focusable to false on a father TreeViewItems also prevents them from being selected.