Tab Navigation on Virtualized Items Panel - c#

How can you set the Tab Navigation on virtualized items? As example;
<ListBox x:Name="Items">
<ListBox.Template>
<ControlTemplate>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel VirtualizingStackPanel.VirtualizationMode="Recycling"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Button />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If I set TabNavigation=Once or Cycle on the Scrollviewer itself, or the Listbox parent etc, it only tabs through items available in the viewport since the others haven't been generated yet. Is there a trick someone might share for when tabbing through Item Objects it will allow Tab to proceed to the next not-yet-virtualized item while bringing it to view in the viewport and providing intuitive tabbing through the controls?

So basically what was came up (thanks to the extra eyes and help of another fine dev) with was to go ahead and render the other items but while remaining virtualized from onload with a custom behavior and at the same time exposing a dependency for continuous scrolling and bringing the current item into view in the viewport;
namespace The.Namespace
{
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
/// <summary>
/// Scroll selected item into view.
/// </summary>
public class ListBoxFocusBehavior : FocusBehavior<ListBox>
{
public static readonly DependencyProperty IsContinuousProperty = DependencyProperty.Register("IsContinuous",
typeof(bool),
typeof(ListBoxFocusBehavior),
new PropertyMetadata(
false,
(d, e) => ((ListBoxFocusBehavior)d).IsContinuousScroll = (bool)e.NewValue));
/// <summary>
/// Gets or sets a value indicating whether this instance is continuous.
/// </summary>
/// <value>
/// <c>true</c> if this instance is continuous; otherwise, <c>false</c>.
/// </value>
public bool IsContinuous
{
get { return (bool)GetValue(IsContinuousProperty); }
set { SetValue(IsContinuousProperty, value); }
}
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += SelectionChanged;
AssociatedObject.KeyDown += KeyDown;
}
/// <summary>
/// Keys down.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> instance containing the event data.</param>
private void KeyDown(object sender, KeyEventArgs e)
{
e.Handled = false;
if (e.Key == Key.Tab && Keyboard.Modifiers == ModifierKeys.None)
{
//forward tab ...
var idx = AssociatedObject.Items.IndexOf(AssociatedObject.SelectedItem);
if (idx < AssociatedObject.Items.Count-1)
{
AssociatedObject.SelectedItem = AssociatedObject.Items[idx + 1];
e.Handled = true;
}
}
if (e.Key == Key.Tab && (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
{
//back tab.
var idx = AssociatedObject.Items.IndexOf(AssociatedObject.SelectedItem);
if (idx > 0)
{
AssociatedObject.SelectedItem = AssociatedObject.Items[idx - 1];
e.Handled = true;
}
}
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= SelectionChanged;
AssociatedObject.KeyDown -= KeyDown;
}
/// <summary>
/// Gots the focus.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
private void GotFocus(object sender, RoutedEventArgs e)
{
if (AssociatedObject.SelectedItem == null && AssociatedObject.Items.Any())
{
AssociatedObject.SelectedItem = AssociatedObject.Items.First();
}
}
/// <summary>
/// Selections the changed.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
private void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (AssociatedObject.SelectedItem == null) return;
AssociatedObject.UpdateLayout();
//have to, otherwise the listbox will probably not focus.
Action setFocus = () =>
{
AssociatedObject.UpdateLayout();
AssociatedObject.ScrollIntoView(AssociatedObject.SelectedItem);
//ensure that if the container did not exist yet (virtualized), it gets created.
AssociatedObject.UpdateLayout();
var container =
AssociatedObject.ItemContainerGenerator.ContainerFromItem(
AssociatedObject.SelectedItem) as Control;
if (container != null)
{
container.Focus();
}
};
AssociatedObject.Dispatcher.BeginInvoke(setFocus);
}
}
}

Related

Extending TextBox in WPF using MahApps keeping Style

I made a custom textbox class for validating the input of the user to only allow Hexadecimal values, and used this new textbox (HexTextBox) in the xaml. It works well, but the HexTextBox looses all the style from the Mahapps, including color scheme and TextBoxHelper. Do you know how to use this extended TexBox and keep the style?
HexTextBox:
public class HexTextBox : TextBox
{
public HexTextBox()
{
}
/// <summary>
/// Raise when a keyboard key is pressed.
/// </summary>
/// <param name="e">The event args.</param>
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Space)
{
e.Handled = true;
}
base.OnPreviewKeyDown(e);
}
/// <summary>
/// Raise when a text will be inputed in the text box object.
/// </summary>
/// <param name="e">The event args.</param>
protected override void OnTextInput(TextCompositionEventArgs e)
{
int hexNumber;
e.Handled = !int.TryParse(e.Text, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out hexNumber);
base.OnTextInput(e);
}
}
Window.xaml
<UserControl
...
xmlns:CoreWPF="clr-namespace:CoreWPF;assembly=CoreWPF"
...>
<CoreWPF:HexTextBox
Text="{Binding DataXor1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="2" Grid.Row="0"
controls:TextBoxHelper.ClearTextButton="True"
Height="26"
TextWrapping="Wrap"
CharacterCasing="Upper"
VerticalAlignment="Center"/>
Thanks in advance!
Create default style for your custom control which will be based on TextBox style.
<Style TargetType="Controls:HexTextBox" BasedOn="{StaticResource {x:Type TextBox}}"/>

how to show Loader on the xaml using mvvm

Hi I have integrated usercontrol(Loader) on my xaml page.
I want to show this loader on the fronpage.
if some another pages is loading.
<control:LoadingAnimation x:Name="ldrControl" Margin="100,100,100,150" Visibility="{Binding IsLoaderVisibile}" />
I am able to show this control but when another pages are loading then It's hided.
I have to show this control in front of the pages.
this will work ,
xmlns:local="clr-namespace:WpfApplication156"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Width="{Binding RelativeSource={RelativeSource Self},
Path=Height,
Mode=TwoWay}"
Height="120"
Background="Transparent"
IsVisibleChanged="HandleVisibleChanged"
Opacity="0"
Visibility="Hidden">
<Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Canvas Width="120"
Height="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Loaded="HandleLoaded"
RenderTransformOrigin="0.5,0.5"
Unloaded="HandleUnloaded">
<Canvas.Resources>
<Style TargetType="Ellipse">
<Setter Property="Width" Value="20" />
<Setter Property="Height" Value="20" />
<Setter Property="Stretch" Value="Fill" />
<Setter Property="Fill">
<Setter.Value>
<Binding Path="Foreground">
<Binding.RelativeSource>
<RelativeSource AncestorType="{x:Type local:ProgressBar}" Mode="FindAncestor" />
</Binding.RelativeSource>
</Binding>
</Setter.Value>
</Setter>
</Style>
</Canvas.Resources>
<Ellipse x:Name="C0" Opacity="1.0" />
<Ellipse x:Name="C1" Opacity="0.9" />
<Ellipse x:Name="C2" Opacity="0.8" />
<Ellipse x:Name="C3" Opacity="0.7" />
<Ellipse x:Name="C4" Opacity="0.6" />
<Ellipse x:Name="C5" Opacity="0.5" />
<Ellipse x:Name="C6" Opacity="0.4" />
<Ellipse x:Name="C7" Opacity="0.3" />
<Ellipse x:Name="C8" Opacity="0.2" />
<Canvas.RenderTransform>
<RotateTransform x:Name="SpinnerRotate" Angle="0" />
</Canvas.RenderTransform>
</Canvas>
</Viewbox>
</UserControl>
public partial class ProgressBar
{
#region Public Fields
/// <summary>
/// Spinning Speed. Default is 60, that's one rotation per second.
/// </summary>
public static readonly DependencyProperty RotationsPerMinuteProperty =
DependencyProperty.Register(
"RotationsPerMinute",
typeof(double),
typeof(ProgressBar),
new PropertyMetadata(60.0));
/// <summary>
/// Startup time in milliseconds, default is a second.
/// </summary>
public static readonly DependencyProperty StartupDelayProperty =
DependencyProperty.Register(
"StartupDelay",
typeof(int),
typeof(ProgressBar),
new PropertyMetadata(1000));
#endregion Public Fields
#region Private Fields
/// <summary>
/// Timer for the Animation.
/// </summary>
private readonly DispatcherTimer animationTimer;
/// <summary>
/// Mouse Cursor.
/// </summary>
private Cursor originalCursor;
#endregion Private Fields
#region Public Constructors
/// <summary>
/// Initializes a new instance of the ProgressBar class.
/// </summary>
public ProgressBar()
{
InitializeComponent();
this.animationTimer = new DispatcherTimer(DispatcherPriority.Normal, Dispatcher);
}
#endregion Public Constructors
#region Public Properties
/// <summary>
/// Gets or sets the spinning speed. Default is 60, that's one rotation per second.
/// </summary>
public double RotationsPerMinute
{
get
{
return (double)this.GetValue(RotationsPerMinuteProperty);
}
set
{
this.SetValue(RotationsPerMinuteProperty, value);
}
}
/// <summary>
/// Gets or sets the startup time in milliseconds, default is a second.
/// </summary>
public int StartupDelay
{
get
{
return (int)this.GetValue(StartupDelayProperty);
}
set
{
this.SetValue(StartupDelayProperty, value);
}
}
#endregion Public Properties
#region Private Methods
/// <summary>
/// Apply a single rotation transformation.
/// </summary>
/// <param name="sender">Sender of the Event: the Animation Timer.</param>
/// <param name="e">Event arguments.</param>
private void HandleAnimationTick(object sender, EventArgs e)
{
this.SpinnerRotate.Angle = (this.SpinnerRotate.Angle + 36) % 360;
}
/// <summary>
/// Control was loaded: distribute circles.
/// </summary>
/// <param name="sender">Sender of the Event: I wish I knew.</param>
/// <param name="e">Event arguments.</param>
private void HandleLoaded(object sender, RoutedEventArgs e)
{
this.SetPosition(C0, 0.0);
this.SetPosition(C1, 1.0);
this.SetPosition(C2, 2.0);
this.SetPosition(C3, 3.0);
this.SetPosition(C4, 4.0);
this.SetPosition(C5, 5.0);
this.SetPosition(C6, 6.0);
this.SetPosition(C7, 7.0);
this.SetPosition(C8, 8.0);
}
/// <summary>
/// Control was unloaded: stop spinning.
/// </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="e">Event arguments.</param>
private void HandleUnloaded(object sender, RoutedEventArgs e)
{
this.StopSpinning();
}
/// <summary>
/// Visibility property was changed: start or stop spinning.
/// </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="e">Event arguments.</param>
private void HandleVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// Don't give the developer a headache.
////if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
////{
//// return;
////}
bool isVisible = (bool)e.NewValue;
if (isVisible)
{
this.StartDelay();
}
else
{
this.StopSpinning();
}
}
/// <summary>
/// Calculate position of a circle.
/// </summary>
/// <param name="ellipse">The circle.</param>
/// <param name="sequence">Sequence number of the circle.</param>
private void SetPosition(Ellipse ellipse, double sequence)
{
ellipse.SetValue(
Canvas.LeftProperty,
50.0 + (Math.Sin(Math.PI * ((0.2 * sequence) + 1)) * 50.0));
ellipse.SetValue(
Canvas.TopProperty,
50 + (Math.Cos(Math.PI * ((0.2 * sequence) + 1)) * 50.0));
}
/// <summary>
/// Startup Delay.
/// </summary>
private void StartDelay()
{
this.originalCursor = Mouse.OverrideCursor;
Mouse.OverrideCursor = Cursors.Wait;
// Startup
this.animationTimer.Interval = new TimeSpan(0, 0, 0, 0, this.StartupDelay);
this.animationTimer.Tick += this.StartSpinning;
this.animationTimer.Start();
}
/// <summary>
/// Start Spinning.
/// </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="e">Event Arguments.</param>
private void StartSpinning(object sender, EventArgs e)
{
this.animationTimer.Stop();
this.animationTimer.Tick -= this.StartSpinning;
// 60 secs per minute, 1000 millisecs per sec, 10 rotations per full circle:
this.animationTimer.Interval = new TimeSpan(0, 0, 0, 0, (int)(6000 / this.RotationsPerMinute));
this.animationTimer.Tick += this.HandleAnimationTick;
this.animationTimer.Start();
this.Opacity = 1;
Mouse.OverrideCursor = this.originalCursor;
}
/// <summary>
/// The control became invisible: stop spinning (animation consumes CPU).
/// </summary>
private void StopSpinning()
{
this.animationTimer.Stop();
this.animationTimer.Tick -= this.HandleAnimationTick;
this.Opacity = 0;
}
#endregion Private Methods
}
//********************************************
IsLoaded=true; set in Relay Command
IsLoaded =false;
private Visibility isLoaded;
public Visibility IsLoaded
{
get
{
return this.isLoaded;
}
set
{
this.isLoaded = value;
RasisePropertyChange("IsLoaded");
}
}
<vm:ProgressBar x:Name="ProgressBar"
Grid.Row="2"
Grid.Column="1"
Width="140"
Margin="12"
Padding="10"
Visibility="{Binding IsLoaded}">
<vm:ProgressBar.Foreground>
<RadialGradientBrush Center="0.5,0.5" GradientOrigin="0.4,0.4" RadiusX="0.5" RadiusY="0.5">
<RadialGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="1" Color="DimGray" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</vm:ProgressBar.Foreground>
</vm:ProgressBar>

Update TextBlock with binding not working

I have a question about databinding!
I am writing code for a 'node editor' that has some (different) nodes in it.
I use a BaseViewModel class that derives from INotifyPropertyChanged.
There is a 'base' NodeViewModel (that derives from it) with an ObservableCollection and other Properties, like the Node's Name property. It's implementation looks like this:
(in public class NodeViewModel : BaseViewModel):
protected String mName = String.Empty;
public String Name {
get { return mName; }
set {
if (mName == value) {
return;
}
mName = value;
OnPropertyChanged("Name");
}
}
With an OnPropertyChanged handler that looks like this:
(in BaseViewModel)
protected virtual void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Now I have one additional RoomViewModel that derives from NodeViewModel.
I use another different ViewModel that I call RoomCollectionViewModel to group some rooms.
Now when I add a room to my roomcollection (by drawing a connection between them) I test all connected rooms for the same name.
If an already connected room exists in the collection with the same room name (e.g. "new room") I want to change those two room's names to e.g. "new room #1" and "new room #2". No problem so far.
Every node control (created using DataTemplates with set DataContext set to the ViewModel) contains a TextBlock (a modified one) that displays the node's name.
This is where it gets problematic:
I use a modified Textblock because I want to be able to modify the node's name by double-clicking on it. And that works perfectly, only if I modify the RoomViewModel's name in Code, this (modified) TextBlock won't update.
The strange thing is this:
When two equally named rooms in a collection get renamed by my code and I then double-click on the editable TextBlock (which converts to a TextBox in that process), I already see the modified Text. So I assume my DataBinding and my code is correct, just not complete :)
So how is it possible to force an update of my EditableTextBlock, the Text (DependencyProperty) seems to be updated correctly...
I hope you understand what my problem is! Thank you for any help.
Update 1
This is the XAML code for my EditableTextBlock (it comes from here: http://www.codeproject.com/Articles/31592/Editable-TextBlock-in-WPF-for-In-place-Editing)
<UserControl x:Class="NetworkUI.EditableTextBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:NetworkUI"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="240" x:Name="mainControl">
<UserControl.Resources>
<DataTemplate x:Key="EditModeTemplate">
<TextBox KeyDown="TextBox_KeyDown" Loaded="TextBox_Loaded" LostFocus="TextBox_LostFocus"
Text="{Binding ElementName=mainControl, Path=Text, UpdateSourceTrigger=PropertyChanged}"
Margin="0" BorderThickness="1" />
</DataTemplate>
<DataTemplate x:Key="DisplayModeTemplate">
<TextBlock Text="{Binding ElementName=mainControl, Path=FormattedText}" Margin="5,3,5,3" MouseDown="TextBlock_MouseDown" />
</DataTemplate>
<Style TargetType="{x:Type local:EditableTextBlock}">
<Style.Triggers>
<Trigger Property="IsInEditMode" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}" />
</Trigger>
<Trigger Property="IsInEditMode" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource DisplayModeTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
And here is the code-behind file:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace NetworkUI {
/// <summary>
/// Interaction logic for EditableTextBlock.xaml
/// </summary>
public partial class EditableTextBlock : UserControl {
#region Dependency Properties, Events
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(EditableTextBlock),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty IsEditableProperty =
DependencyProperty.Register("IsEditable", typeof(Boolean), typeof(EditableTextBlock), new PropertyMetadata(true));
public static readonly DependencyProperty IsInEditModeProperty =
DependencyProperty.Register("IsInEditMode", typeof(Boolean), typeof(EditableTextBlock), new PropertyMetadata(false));
public static readonly DependencyProperty TextFormatProperty =
DependencyProperty.Register("TextFormat", typeof(String), typeof(EditableTextBlock), new PropertyMetadata("{0}"));
#endregion ///Dependency Properties, Events
#region Variables and Properties
/// <summary>
/// We keep the old text when we go into editmode
/// in case the user aborts with the escape key
/// </summary>
private String oldText;
/// <summary>
/// Text content of this EditableTextBlock
/// </summary>
public String Text {
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// Is this EditableTextBlock editable or not
/// </summary>
public Boolean IsEditable {
get { return (Boolean)GetValue(IsEditableProperty); }
set { SetValue(IsEditableProperty, value); }
}
/// <summary>
/// Is this EditableTextBlock currently in edit mode
/// </summary>
public Boolean IsInEditMode {
get {
if (IsEditable)
return (Boolean)GetValue(IsInEditModeProperty);
else
return false;
}
set {
if (IsEditable) {
if (value)
oldText = Text;
SetValue(IsInEditModeProperty, value);
}
}
}
/// <summary>
/// The text format for the TextBlock
/// </summary>
public String TextFormat {
get { return (String)GetValue(TextFormatProperty); }
set {
if (value == "")
value = "{0}";
SetValue(TextFormatProperty, value);
}
}
/// <summary>
/// The formatted text of this EditablTextBlock
/// </summary>
public String FormattedText {
get { return String.Format(TextFormat, Text); }
}
#endregion ///Variables and Properties
#region Constructor
/// <summary>
/// Default constructor for the editable text block
/// </summary>
public EditableTextBlock() {
InitializeComponent();
Focusable = true;
FocusVisualStyle = null;
}
#endregion ///Constructor
#region Methods, Functions and Eventhandler
/// <summary>
/// Invoked when we enter edit mode
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
void TextBox_Loaded(object sender, RoutedEventArgs e) {
TextBox txt = sender as TextBox;
/// Give the TextBox input focus
txt.Focus();
txt.SelectAll();
}
/// <summary>
/// Invoked when we exit edit mode
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
void TextBox_LostFocus(object sender, RoutedEventArgs e) {
IsInEditMode = false;
}
/// <summary>
/// Invoked when the user edits the annotation.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
void TextBox_KeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
IsInEditMode = false;
e.Handled = true;
}
else if (e.Key == Key.Escape) {
IsInEditMode = false;
Text = oldText;
e.Handled = true;
}
}
/// <summary>
/// Invoked when the user double-clicks on the textblock
/// to edit the text
/// </summary>
/// <param name="sender">Sender (the Textblock)</param>
/// <param name="e">Event arguments</param>
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e) {
if (e.ClickCount == 2)
IsInEditMode = true;
}
#endregion ///Methods, Functions and Eventhandler
}
Thank you for any help!
Update 2
I changed the following line of code:
<TextBlock Text="{Binding ElementName=mainControl, Path=FormattedText}" Margin="5,3,5,3" MouseDown="TextBlock_MouseDown" />
to:
<TextBlock Text="{Binding ElementName=mainControl, Path=Text}" Margin="5,3,5,3" MouseDown="TextBlock_MouseDown" />
and now it is working!
I didn't see the TextBlock using the FormattedText in the first place! Ugh, thank you very much, now everything updates perfectly!
As postes by Lee O. the problem was indeed the bound property from my EditableTextBlock control.
The TextBlock used the FormattedText property that was updated by my Binding. Now I use the Text property for both, the TextBlock and the TextBox controls.
I simply removed the FormattedText property as well as the TextFormatProperty (DependencyProperty) and TextFormat property from my EditableTextBlock because I didn't plan to use those.
Thank you again!

Bindings inside WPF user control

In order to get into the WPF world and getting used to bindings, I've made a user control used to define a search filter. Depending on the wanted filter, the user can either enter a text, pick a date or select an item in a combo box. Here's an example with three instances of the created search control, each being of different type:
The good news is, everything is working but I'm not sure if everything has been done as intended.
SearchUserControl.xaml:
<UserControl x:Class="Zefix.View.UserControls.SearchUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="82"
d:DesignWidth="300"
Height="Auto"
x:Name="SearchUserControlRoot">
<Grid>
<StackPanel>
<Label Name="LabelHeaderText" Content="{Binding HeaderText, ElementName=SearchUserControlRoot}" />
<TextBox Name="TextBoxSearchText" Text="{Binding SearchValue, ElementName=SearchUserControlRoot}" Visibility="{Binding TextBoxVisiblity, ElementName=SearchUserControlRoot}" />
<DatePicker Name="DatePickerSearch" SelectedDate="{Binding SearchValue, ElementName=SearchUserControlRoot}" Visibility="{Binding DatePickerVisiblity, ElementName=SearchUserControlRoot}" />
<ComboBox Name="ComboBoxSearch" Text="{Binding SearchValue, ElementName=SearchUserControlRoot}" ItemsSource="{Binding AvailableValues, ElementName=SearchUserControlRoot}" Visibility="{Binding ComboBoxVisiblity, ElementName=SearchUserControlRoot}" IsEditable="True" />
</StackPanel>
</Grid>
</UserControl>
SearchUserControl.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using Zefix.DataAccess;
namespace Zefix.View.UserControls {
/// <summary>
/// Interaction logic for SearchUserControl.xaml
/// </summary>
public partial class SearchUserControl {
#region Public Dependency Properties
/// <summary>
/// The search value property
/// </summary>
public static readonly DependencyProperty SearchValueProperty =
DependencyProperty.Register("SearchValue", typeof (object), typeof (SearchUserControl));
/// <summary>
/// The available values property
/// </summary>
public static readonly DependencyProperty AvailableValuesProperty =
DependencyProperty.Register("AvailableValues", typeof (IEnumerable<object>), typeof (SearchUserControl));
/// <summary>
/// The search type property
/// </summary>
public static readonly DependencyProperty SearchTypeProperty =
DependencyProperty.Register("SearchType", typeof (SearchType), typeof (SearchUserControl));
/// <summary>
/// The header text property
/// </summary>
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register("HeaderText", typeof (string), typeof (SearchUserControl));
#endregion
#region Private Dependency Properties
/// <summary>
/// The combo box visiblity property
/// </summary>
private static readonly DependencyProperty ComboBoxVisiblityProperty =
DependencyProperty.Register("ComboBoxVisiblity", typeof (Visibility), typeof (SearchUserControl));
/// <summary>
/// The text box visiblity property
/// </summary>
private static readonly DependencyProperty TextBoxVisiblityProperty =
DependencyProperty.Register("TextBoxVisiblity", typeof (Visibility), typeof (SearchUserControl));
/// <summary>
/// The date picker visiblity property
/// </summary>
private static readonly DependencyProperty DatePickerVisiblityProperty =
DependencyProperty.Register("DatePickerVisiblity", typeof (Visibility), typeof (SearchUserControl));
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the type of the search.
/// </summary>
/// <value>
/// The type of the search.
/// </value>
public SearchType SearchType {
get { return (SearchType) GetValue(SearchTypeProperty); }
set { SetValue(SearchTypeProperty, value); }
}
/// <summary>
/// Gets or sets the header text.
/// </summary>
/// <value>
/// The header text.
/// </value>
public string HeaderText {
get { return (string) GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
/// <summary>
/// Gets or sets the available values.
/// </summary>
/// <value>
/// The available values.
/// </value>
public IEnumerable<object> AvailableValues {
get { return (IEnumerable<object>) GetValue(AvailableValuesProperty); }
set { SetValue(AvailableValuesProperty, value); }
}
/// <summary>
/// Gets or sets the search value.
/// </summary>
/// <value>
/// The search value.
/// </value>
public object SearchValue {
get { return GetValue(SearchValueProperty); }
set { SetValue(SearchValueProperty, value); }
}
#endregion
#region Private Properties
/// <summary>
/// Gets or sets the combo box visiblity.
/// </summary>
/// <value>
/// The combo box visiblity.
/// </value>
private Visibility ComboBoxVisiblity {
get { return (Visibility) GetValue(ComboBoxVisiblityProperty); }
set { SetValue(ComboBoxVisiblityProperty, value); }
}
/// <summary>
/// Gets or sets the date picker visiblity.
/// </summary>
/// <value>
/// The date picker visiblity.
/// </value>
private Visibility DatePickerVisiblity {
get { return (Visibility) GetValue(DatePickerVisiblityProperty); }
set { SetValue(DatePickerVisiblityProperty, value); }
}
/// <summary>
/// Gets or sets the text box visiblity.
/// </summary>
/// <value>
/// The text box visiblity.
/// </value>
private Visibility TextBoxVisiblity {
get { return (Visibility) GetValue(TextBoxVisiblityProperty); }
set { SetValue(TextBoxVisiblityProperty, value); }
}
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="SearchUserControl" /> class.
/// </summary>
public SearchUserControl() {
InitializeComponent();
DependencyPropertyDescriptor pd = DependencyPropertyDescriptor.FromProperty(SearchTypeProperty, typeof (SearchUserControl));
pd.AddValueChanged(this, OnSearchTypePropertyChanged);
// Initialize default parameters
SearchType = SearchType.Unknown;
}
#endregion
#region Private Methods
/// <summary>
/// Called when the search type property has changed.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void OnSearchTypePropertyChanged(object sender, EventArgs e) {
// Hide all editors
DatePickerVisiblity = Visibility.Collapsed;
ComboBoxVisiblity = Visibility.Collapsed;
TextBoxVisiblity = Visibility.Collapsed;
// Make the correct editor visible
switch (SearchType) {
case SearchType.Date:
DatePickerVisiblity = Visibility.Visible;
break;
case SearchType.TextSelection:
ComboBoxVisiblity = Visibility.Visible;
break;
case SearchType.Text:
TextBoxVisiblity = Visibility.Visible;
break;
}
}
#endregion
}
}
Instantiation of the search controls from the parent control:
<ribbon:Tab Label="Search">
<ribbon:Group Padding="0,5,0,5">
<customcontrols:SearchUserControl x:Name="SearchUserControlCompanyName" HeaderText="company name" Margin="5,0,0,0" SearchType="Text" VerticalAlignment="Center" VerticalContentAlignment="Center" />
<customcontrols:SearchUserControl x:Name="SearchUserControlCompanyNationality" HeaderText="company nationality (ISO3 code)" Margin="5,0,0,0" SearchType="TextSelection" AvailableValues="{Binding Path=CompaniesViewModel.ISO3Codes}" VerticalAlignment="Center" />
<customcontrols:SearchUserControl x:Name="SearchUserControlDateFounded" HeaderText="date founded" Margin="5,0,0,0" SearchType="Date" VerticalAlignment="Center" VerticalContentAlignment="Center" />
<ribbon:Button Context="StatusBarItem" Name="ButtonApplyFilter" Label="Search" ImageSourceSmall="/Resources/search_magnifying_glass_find.png" Margin="5,0,0,0" VerticalAlignment="Center" Click="OnButtonApplyFilterClicked" Command="{Binding Path=ApplyFilterCommand}" ScreenTipHeader="Apply the search filter" VerticalContentAlignment="Center" VariantSize="Large" />
</ribbon:Group>
</ribbon:Tab>
In the SearchControl I wanted to display the correct component (textbox, datepicker or combobox) according to the set SearchType. For this the, xxxVisibility dependency properties and properties have been created (they are being set when the SearchTypeProperty notifies a property changed event). As there is no reason to expose them as public (they are being used only inside the SearchControl), I've made them private; MSDN states that bound properties MUST be public though. The project compiles and runs without an issue, but errors are being shown for the bound xxxVisibility properties with the message 'Public member expected' (can't tell if it's visual studio or resharper telling me that).
Is my approach to create this user control correct in respect to the WPF concepts?
Should the xxxVisibility properties be public (event though I don't want to expose them)?
This is a very difficult question to 'answer', rather than just 'comment' on. In my personal opinion, your UserControl has been written well and as far as I can see doesn't break any rules. Although I don't see any problem with declaring a private DependencyProperty, it is unusual. In this situation, developers often chose to implement a public Read Only DependencyProperty with a private DependencyPropertyKey instead:
private static readonly DependencyPropertyKey ComboBoxVisiblityPropertyKey
= DependencyProperty.RegisterReadOnly("ComboBoxVisiblity", typeof(int),
typeof(SearchUserControl), new PropertyMetadata(Visibility.Collapsed));
public static readonly DependencyProperty ComboBoxVisiblityProperty
= ComboBoxVisiblityPropertyKey.DependencyProperty;
public int ComboBoxVisiblity
{
get { return (int)GetValue(ComboBoxVisiblityProperty); }
protected set { SetValue(ComboBoxVisiblityPropertyKey, value); }
}
Some developers may also think it unusual that you are creating properties of type Visibility rather than binding bool values with BoolToVisibilityConverters, but again... that is your prerogative. Overall, well done! :)

Binding to a Separate DataContext

I have a MainWindow that contains a window with a TreeView. The tree view binds to an observable collection that I set in the DataContext.
<TreeView ItemsSource="{Binding Trees}" Name="fileTree" MouseDoubleClick="FileTreeMouseDoubleClick" SelectedValuePath="NodePath">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:TreeNodeViewModel}" ItemsSource="{Binding Children}">
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
However, I want to put a separate tree as a child of the MainWindow, which will bind to a different object, how can I do that If I have already used the DataContext property of the MainWindow.xaml?
EDIT: Extension to Question
Now I have:
<TreeView Name="viewTree" ItemsSource="{Binding ViewListTrees, Source=viewListTreeViewModel}">
Where viewListTreeViewModel is a member variable in MainWindow.xaml.cs:
private ViewListTreeViewModel viewListTreeViewModel;
which has the following accessor:
public ObservableCollection<ViewListTreeNodeViewModel> ViewListTrees
{
get { return this.tree; }
}
and ViewListTreeNodeViewModel has:
public string NodeName { get; }
public string NodeImage { get; }
My hierarchical data template now looks like:
<HierarchicalDataTemplate DataType="{x:Type local:ViewListTreeNodeViewModel}" ItemsSource="{Binding Children}">
<StackPanel>
<Image Source="{Binding NodeImage}" />
<TextBlock Text="{Binding NodeName}"/>
</StackPanel>
</HierarchicalDataTemplate>
Simply expose two Properties on a class you bind to your Window (rather than binding the collection directly), exposing ObservableCollection properties; Trees and SeperateTree and bind each TreeView accordingly:
<Window>
<Grid>
<TreeView ItemsSource="{Binding Trees}">
...
</TreeView>
<TreeView ItemsSource="{Binding SeperateTree}">
...
</TreeView>
</Grid>
</Window>
You can either bind directly to the object using the Source part of the Binding or you can set another DataContext locally on the TreeView.
Example 1
<TreeView ItemsSource="{Binding Trees, Source=YourOtherDataContext}"/>
Example 2
<TreeView ItemsSource="{Binding Trees}"
DataContext="{Binding Path=YourOtherDataContext}"/>
As promised in the comments, here is an example of a base class to use for your view models.
Usage
public string Name
{
get { return name; }
set { SetValue(ref name, value, "Name"); }
}
ObservableObject
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Compares the value and sets iff it has changed.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="field">The field.</param>
/// <param name="value">The value.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns><c>True</c> if the field was changed</returns>
protected virtual bool SetValue<T>(ref T field, T value, string propertyName)
{
return SetValue(ref field, value, propertyName, true);
}
/// <summary>
/// Compares the value and sets iff it has changed.
/// </summary>
/// <param name="field">The field.</param>
/// <param name="value">The value.</param>
/// <param name="propertyName">Name of the property.</param>
/// <param name="checkForEquality">if set to <c>true</c> [check for equality].</param>
/// <returns><c>True</c> if the field was changed</returns>
protected virtual bool SetValue<T>(ref T field, T value, string propertyName, bool checkForEquality)
{
if (checkForEquality && EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Sets the value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="setAction">The set action.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
protected virtual bool SetValue(Action setAction, string propertyName)
{
return SetValue(setAction, null, propertyName);
}
/// <summary>
/// Sets the value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="setAction">The set action.</param>
/// <param name="equalityFunc">The equality func.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
protected virtual bool SetValue(Action setAction, Func<bool> equalityFunc, string propertyName)
{
if (equalityFunc != null && !equalityFunc.Invoke())
return false;
setAction.Invoke();
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(this, propertyName);
}
protected void OnPropertyChanged(object source, string propertyName)
{
// copying the event handlers before that this is "thread safe"
// http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}

Categories

Resources