I have a TabControl with a custom ItemTemplate (for the TabItem).
I would like to hide the last tab item and if I could bind to the TabItem's Visibility property this would be possible.
Any suggestions?
I figured out a way to do this with a behavior. I would have preferred a cleaner solution such as a binding, but it works.
<telerik:RadTabControl x:Name="myRadTabControl">
<telerik:RadTabControl.ItemTemplate>
<!-- Tab Item Header -->
<DataTemplate>
<TextBlock>
<i:Interaction.Behaviors>
<Behaviors:MakeLastTabItemInvisible ParentRadTabControl="{Binding ElementName=myRadTabControl}" />
</i:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</telerik:RadTabControl>
</telerik:RadTabControl x:Name="myRadTabControl">
public class MakeLastTabItemInvisible : Behavior<FrameworkElement>
{
#region ParentRadTabControl Dependency Property
/// <summary>
/// ParentRadTabControl
/// </summary>
public RadTabControl ParentRadTabControl
{
get { return (RadTabControl)GetValue(ParentRadTabControlProperty); }
set { SetValue(ParentRadTabControlProperty, value); }
}
/// <summary>
/// ParentRadTabControl Dependency Property.
/// </summary>
public static readonly DependencyProperty ParentRadTabControlProperty =
DependencyProperty.Register(
"ParentRadTabControl",
typeof(RadTabControl),
typeof(MakeLastTabItemInvisible),
new PropertyMetadata(new PropertyChangedCallback(ParentRadTabControlChanged)));
private static void ParentRadTabControlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MakeLastTabItemInvisible c = d as MakeLastTabItemInvisible;
if (c != null)
{
if (e.NewValue == null) return;
var parentTabControl = (RadTabControl)e.NewValue;
if (parentTabControl.Items.Count <= 0) return;
var lastTabItem = parentTabControl.ItemContainerGenerator.ContainerFromIndex(parentTabControl.Items.Count - 1) as RadTabItem;
if (lastTabItem != null) lastTabItem.Visibility = Visibility.Collapsed;
}
}
#endregion
}
Related
I have a TabControl with multiple tabs. Each tab has a multiline TextBox in it.
So in the visible textbox in Tab 1, the insertion point starts out at the beginning of line 1.
I move the insertion point to the end of line 2.
I change the selected tab to Tab 2, and then back to Tab 1.
The insertion point in the Tab 1 TextBox is always back at the start of line one.
Both tabs are being populated by viewmodels in a list:
<TabControl ItemsSource="{Binding TabItemViewModelList}">
<TabControl.Resources>
<DataTemplate DataType="TabItemViewModel">
<Grid>
<TextBox Text="{Binding Text}" />
</Grid>
</DataTemplate>
</TabControl.Resources>
<TabControl>
That's simplified, but not by much.
Is this because it's re-instantiating the template each time I change the selected item, or something like that? Is there any way around that other than agonizingly maintaining every tiny bit of UI state in the ViewModel?
Update
I was right, this is the virtualization bug, it's a known bug, and the designer probably considered it a feature. He's insane, of course: A tab control is not a list box. It becomes totally unusable long before you've got enough items in it for virtualization to make any sense at all.
The answer seems to be Ivan Krivyakov's TabContent.IsCached attached property, found in this question: Turning off Virtualization for Tabcontrol without itemsource - WPF. It's not the answer to his question, but it is the answer to mine.
Looks like this in the XAML:
<TabControl
xmlns:ikriv="clr-namespace:IKriv.Windows.Controls.Behaviors"
ikriv:TabContent.IsCached="True"
ItemsSource="{Binding DocumentList.Documents}"
SelectedItem="{Binding DocumentList.ActiveDocument}"
/>
And here's the complete source code to Krivyakov's solution, just in case CodeProject gets hit by an asteroid:
// TabContent.cs, version 1.2
// The code in this file is Copyright (c) Ivan Krivyakov
// See http://www.ikriv.com/legal.php for more information
//
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
/// <summary>
/// http://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization
/// </summary>
namespace IKriv.Windows.Controls.Behaviors
{
/// <summary>
/// Attached properties for persistent tab control
/// </summary>
/// <remarks>By default WPF TabControl bound to an ItemsSource destroys visual state of invisible tabs.
/// Set ikriv:TabContent.IsCached="True" to preserve visual state of each tab.
/// </remarks>
public static class TabContent
{
public static bool GetIsCached(DependencyObject obj)
{
return (bool)obj.GetValue(IsCachedProperty);
}
public static void SetIsCached(DependencyObject obj, bool value)
{
obj.SetValue(IsCachedProperty, value);
}
/// <summary>
/// Controls whether tab content is cached or not
/// </summary>
/// <remarks>When TabContent.IsCached is true, visual state of each tab is preserved (cached), even when the tab is hidden</remarks>
public static readonly DependencyProperty IsCachedProperty =
DependencyProperty.RegisterAttached("IsCached", typeof(bool), typeof(TabContent), new UIPropertyMetadata(false, OnIsCachedChanged));
public static DataTemplate GetTemplate(DependencyObject obj)
{
return (DataTemplate)obj.GetValue(TemplateProperty);
}
public static void SetTemplate(DependencyObject obj, DataTemplate value)
{
obj.SetValue(TemplateProperty, value);
}
/// <summary>
/// Used instead of TabControl.ContentTemplate for cached tabs
/// </summary>
public static readonly DependencyProperty TemplateProperty =
DependencyProperty.RegisterAttached("Template", typeof(DataTemplate), typeof(TabContent), new UIPropertyMetadata(null));
public static DataTemplateSelector GetTemplateSelector(DependencyObject obj)
{
return (DataTemplateSelector)obj.GetValue(TemplateSelectorProperty);
}
public static void SetTemplateSelector(DependencyObject obj, DataTemplateSelector value)
{
obj.SetValue(TemplateSelectorProperty, value);
}
/// <summary>
/// Used instead of TabControl.ContentTemplateSelector for cached tabs
/// </summary>
public static readonly DependencyProperty TemplateSelectorProperty =
DependencyProperty.RegisterAttached("TemplateSelector", typeof(DataTemplateSelector), typeof(TabContent), new UIPropertyMetadata(null));
[EditorBrowsable(EditorBrowsableState.Never)]
public static TabControl GetInternalTabControl(DependencyObject obj)
{
return (TabControl)obj.GetValue(InternalTabControlProperty);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetInternalTabControl(DependencyObject obj, TabControl value)
{
obj.SetValue(InternalTabControlProperty, value);
}
// Using a DependencyProperty as the backing store for InternalTabControl. This enables animation, styling, binding, etc...
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly DependencyProperty InternalTabControlProperty =
DependencyProperty.RegisterAttached("InternalTabControl", typeof(TabControl), typeof(TabContent), new UIPropertyMetadata(null, OnInternalTabControlChanged));
[EditorBrowsable(EditorBrowsableState.Never)]
public static ContentControl GetInternalCachedContent(DependencyObject obj)
{
return (ContentControl)obj.GetValue(InternalCachedContentProperty);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetInternalCachedContent(DependencyObject obj, ContentControl value)
{
obj.SetValue(InternalCachedContentProperty, value);
}
// Using a DependencyProperty as the backing store for InternalCachedContent. This enables animation, styling, binding, etc...
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly DependencyProperty InternalCachedContentProperty =
DependencyProperty.RegisterAttached("InternalCachedContent", typeof(ContentControl), typeof(TabContent), new UIPropertyMetadata(null));
[EditorBrowsable(EditorBrowsableState.Never)]
public static object GetInternalContentManager(DependencyObject obj)
{
return (object)obj.GetValue(InternalContentManagerProperty);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetInternalContentManager(DependencyObject obj, object value)
{
obj.SetValue(InternalContentManagerProperty, value);
}
// Using a DependencyProperty as the backing store for InternalContentManager. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InternalContentManagerProperty =
DependencyProperty.RegisterAttached("InternalContentManager", typeof(object), typeof(TabContent), new UIPropertyMetadata(null));
private static void OnIsCachedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj == null) return;
var tabControl = obj as TabControl;
if (tabControl == null)
{
throw new InvalidOperationException("Cannot set TabContent.IsCached on object of type " + args.NewValue.GetType().Name +
". Only objects of type TabControl can have TabContent.IsCached property.");
}
bool newValue = (bool)args.NewValue;
if (!newValue)
{
if (args.OldValue != null && ((bool)args.OldValue))
{
throw new NotImplementedException("Cannot change TabContent.IsCached from True to False. Turning tab caching off is not implemented");
}
return;
}
EnsureContentTemplateIsNull(tabControl);
tabControl.ContentTemplate = CreateContentTemplate();
EnsureContentTemplateIsNotModified(tabControl);
}
private static DataTemplate CreateContentTemplate()
{
const string xaml =
"<DataTemplate><Border b:TabContent.InternalTabControl=\"{Binding RelativeSource={RelativeSource AncestorType=TabControl}}\" /></DataTemplate>";
var context = new ParserContext();
context.XamlTypeMapper = new XamlTypeMapper(new string[0]);
context.XamlTypeMapper.AddMappingProcessingInstruction("b", typeof(TabContent).Namespace, typeof(TabContent).Assembly.FullName);
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("b", "b");
var template = (DataTemplate)XamlReader.Parse(xaml, context);
return template;
}
private static void OnInternalTabControlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj == null) return;
var container = obj as Decorator;
if (container == null)
{
var message = "Cannot set TabContent.InternalTabControl on object of type " + obj.GetType().Name +
". Only controls that derive from Decorator, such as Border can have a TabContent.InternalTabControl.";
throw new InvalidOperationException(message);
}
if (args.NewValue == null) return;
if (!(args.NewValue is TabControl))
{
throw new InvalidOperationException("Value of TabContent.InternalTabControl cannot be of type " + args.NewValue.GetType().Name +", it must be of type TabControl");
}
var tabControl = (TabControl)args.NewValue;
var contentManager = GetContentManager(tabControl, container);
contentManager.UpdateSelectedTab();
}
private static ContentManager GetContentManager(TabControl tabControl, Decorator container)
{
var contentManager = (ContentManager)GetInternalContentManager(tabControl);
if (contentManager != null)
{
/*
* Content manager already exists for the tab control. This means that tab content template is applied
* again, and new instance of the Border control (container) has been created. The old container
* referenced by the content manager is no longer visible and needs to be replaced
*/
contentManager.ReplaceContainer(container);
}
else
{
// create content manager for the first time
contentManager = new ContentManager(tabControl, container);
SetInternalContentManager(tabControl, contentManager);
}
return contentManager;
}
private static void EnsureContentTemplateIsNull(TabControl tabControl)
{
if (tabControl.ContentTemplate != null)
{
throw new InvalidOperationException("TabControl.ContentTemplate value is not null. If TabContent.IsCached is True, use TabContent.Template instead of ContentTemplate");
}
}
private static void EnsureContentTemplateIsNotModified(TabControl tabControl)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(TabControl.ContentTemplateProperty, typeof(TabControl));
descriptor.AddValueChanged(tabControl, (sender, args) =>
{
throw new InvalidOperationException("Cannot assign to TabControl.ContentTemplate when TabContent.IsCached is True. Use TabContent.Template instead");
});
}
public class ContentManager
{
TabControl _tabControl;
Decorator _border;
public ContentManager(TabControl tabControl, Decorator border)
{
_tabControl = tabControl;
_border = border;
_tabControl.SelectionChanged += (sender, args) => { UpdateSelectedTab(); };
}
public void ReplaceContainer(Decorator newBorder)
{
if (Object.ReferenceEquals(_border, newBorder)) return;
_border.Child = null; // detach any tab content that old border may hold
_border = newBorder;
}
public void UpdateSelectedTab()
{
_border.Child = GetCurrentContent();
}
private ContentControl GetCurrentContent()
{
var item = _tabControl.SelectedItem;
if (item == null) return null;
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
if (tabItem == null) return null;
var cachedContent = TabContent.GetInternalCachedContent(tabItem);
if (cachedContent == null)
{
cachedContent = new ContentControl
{
DataContext = item,
ContentTemplate = TabContent.GetTemplate(_tabControl),
ContentTemplateSelector = TabContent.GetTemplateSelector(_tabControl)
};
cachedContent.SetBinding(ContentControl.ContentProperty, new Binding());
TabContent.SetInternalCachedContent(tabItem, cachedContent);
}
return cachedContent;
}
}
}
}
The carret position gets updated every time the text gets re-initialized in your case. You could put a behavior on the text box to set it to the end on focus... see the following: Set the caret/cursor position to the end of the string value WPF textbox
I have a problem with binding in KeyBinding in WPF. I'm developing .net 3.5 project using WPF with MVVM pattern. I have to fire command whenever some letter will be typed. Unfortunately Command and CommandParameter aren't Dependency Properties in this .net version and i can't bind to them. So I've written attached properties to assign command and command parameter from my view model. But binding to them isn't working, when I change binding to text (in command parameter) CommandBindingParameterChanged will rise but it doesn't when there is binding to parameter. I tired to set window's name and pass that to binding but it also didn't work. But when I'll assign the same command to button it works fine. Here is my code snippet:
Attached properties:
public class Helper
{
public static readonly DependencyProperty CommandBindingProperty = DependencyProperty.RegisterAttached("CommandBinding", typeof(ICommand), typeof(Helper), new FrameworkPropertyMetadata(default(ICommand), FrameworkPropertyMetadataOptions.None, CommandChanged));
public static ICommand GetCommandBinding(DependencyObject o)
{
return (ICommand)o.GetValue(CommandBindingProperty);
}
public static void SetCommandBinding(DependencyObject o, ICommand value)
{
o.SetValue(CommandBindingProperty, value);
}
private static void CommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var input = d as InputBinding;
input.Command = (ICommand)e.NewValue;
}
public static readonly DependencyProperty CommandBindingParameterProperty = DependencyProperty.RegisterAttached("CommandBindingParameter", typeof(object), typeof(Helper), new PropertyMetadata(CommandParameterChanged));
private static void CommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var input = d as InputBinding;
if (input != null)
input.CommandParameter = e.NewValue;
}
public static object GetCommandBindingParameter(DependencyObject o)
{
return o.GetValue(CommandBindingParameterProperty);
}
public static void SetCommandBindingParameter(DependencyObject o, object value)
{
o.SetValue(CommandBindingParameterProperty, value);
}
}
ViewModel
public class MainWindowViewModel : ViewModelBase
{
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
RaisePropertyChanged("Text");
}
}
private bool _parameter;
public bool Parameter
{
get { return _parameter; }
set
{
_parameter = value;
RaisePropertyChanged("Parameter");
}
}
public MainWindowViewModel()
{
Parameter = true;
}
private RelayCommand<bool> _someCommand;
public ICommand SomeCommand
{
get { return _someCommand ?? (_someCommand = new RelayCommand<bool>(Execute, CanExecute)); }
}
private bool CanExecute(bool arg)
{
return arg;
}
private void Execute(bool obj)
{
//do something
}
}
XAML:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:Test"
Name="Window"
DataContext="{Binding Main, Source={StaticResource Locator}}"
>
<Grid>
<StackPanel>
<TextBox Text="{Binding Text}">
<TextBox.InputBindings>
<KeyBinding Key="A" local:Helper.CommandBinding="{Binding DataContext.SomeCommand, ElementName=Window}" local:Helper.CommandBindingParameter="{Binding DataContext.Parameter, ElementName=Window}"/>
</TextBox.InputBindings>
</TextBox>
<Button Content="SomeButton" Command="{Binding SomeCommand}" CommandParameter="{Binding Parameter}"/>
</StackPanel>
</Grid>
you may want to try this solution.
Use Blend 3 Interactions, i.e. Add System.Windows.Interactivity & Microsoft.Expression.Interactions.dll as reference into your project. I have tested the changes below. Execute method (defined in ViewModel) is called the movement textbox is keyed in.
Modified XAML:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:Test"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Name="Window">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<local:CommandAction Command="{Binding Path=SomeCommand}" CommandParameter="{Binding Path=Parameter}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</StackPanel>
</Grid>
</Window>
CommandAction.CS: Instead of Helper, use CommandAction. CommandAction is found at this location
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using Microsoft.Expression.Interactivity;
using System.Windows.Interactivity;
namespace Test
{
/// <summary>
/// The CommandAction allows the user to route a FrameworkElement's routed event to a Command.
/// For instance this makes it possible to specify--in Xaml--that right-clicking on a Border
/// element should execute the Application.Close command (this example may not make much sense,
/// but it does illustrate what's possible).
///
/// CommandParameter and CommandTarget properties are provided for consistency with the Wpf
/// Command pattern.
///
/// The action's IsEnabled property will be updated according to the Command's CanExecute value.
///
/// In addition a SyncOwnerIsEnabled property allows the user to specify that the owner element
/// should be enabled/disabled whenever the action is enabled/disabled.
/// </summary>
public class CommandAction : TargetedTriggerAction<FrameworkElement>, ICommandSource
{
#region Properties to Expose
[Category("Command Properties")]
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(CommandAction), new PropertyMetadata(
(ICommand)null, OnCommandChanged));
[Category("Command Properties")]
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
"CommandParameter", typeof(object), typeof(CommandAction), new PropertyMetadata());
[Category("Command Properties")]
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(
"CommandTarget", typeof(IInputElement), typeof(CommandAction), new PropertyMetadata());
[Category("Command Properties")]
public bool SyncOwnerIsEnabled
{
get { return (bool)GetValue(SyncOwnerIsEnabledProperty); }
set { SetValue(SyncOwnerIsEnabledProperty, value); }
}
/// <summary>
/// When SyncOwnerIsEnabled is true then changing CommandAction.IsEnabled will automatically
/// update the owner (Target) IsEnabled property.
/// </summary>
public static readonly DependencyProperty SyncOwnerIsEnabledProperty = DependencyProperty.Register(
"SyncOwnerIsEnabled", typeof(bool), typeof(CommandAction), new PropertyMetadata());
#endregion
#region Command implementation
/// <summary>
/// This is a strong reference to the Command.CanExecuteChanged event handler. The commanding
/// system uses a weak reference and if we don't enforce a strong reference then the event
/// handler will be gc'ed.
/// </summary>
private EventHandler CanExecuteChangedHandler;
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var action = (CommandAction)d;
action.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
}
private void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
UnhookCommand(oldCommand);
if (newCommand != null)
HookCommand(newCommand);
}
private void UnhookCommand(ICommand command)
{
command.CanExecuteChanged -= CanExecuteChangedHandler;
UpdateCanExecute();
}
private void HookCommand(ICommand command)
{
// Save a strong reference to the Command.CanExecuteChanged event handler. The commanding
// system uses a weak reference and if we don't save a strong reference then the event
// handler will be gc'ed.
CanExecuteChangedHandler = new EventHandler(OnCanExecuteChanged);
command.CanExecuteChanged += CanExecuteChangedHandler;
UpdateCanExecute();
}
private void OnCanExecuteChanged(object sender, EventArgs e)
{
UpdateCanExecute();
}
private void UpdateCanExecute()
{
if (Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
IsEnabled = command.CanExecute(CommandParameter, CommandTarget);
else
IsEnabled = Command.CanExecute(CommandParameter);
if (Target != null && SyncOwnerIsEnabled)
Target.IsEnabled = IsEnabled;
}
}
#endregion
protected override void Invoke(object o)
{
if (Command != null)
{
var command = Command as RoutedCommand;
if (command != null)
command.Execute(CommandParameter, CommandTarget);
else
Command.Execute(CommandParameter);
}
}
}
}
Screenshot: if System.Windows.Interactivity & Microsoft.Expression.Interactions.dll are missing in your environment, please install blend. Blend is very easy to isntall and Installation doesn't take much time.
Can anyone show me a simple working example for a WPF MVVM application to set the ItemsSource of combobox B based on the SelectedItem of ComboBox A?
It seems from what I've found on this site that it gets all too complicated all too quickly.
What's the "right" MVVM way to get it done?
Thank you.
EDIT
I updated using Didier's example.
An extract of my XAML:
<ComboBox Name="BrowserStackDesktopOS" ItemsSource="Binding Platforms.AvailableBrowserStackDesktopOSes}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopOSSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopOSVersion" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopOSVersions}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopOSVersionSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopBrowser" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopBrowsers}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopBrowserSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopBrowserVersion" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopBrowserVersions}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopBrowserVersionSelectedValue, Mode=TwoWay}"/>
And an example of my code behind:
public string BrowserStackDesktopOSSelectedValue {
get { return (string)GetValue(BrowserStackDesktopOSSelectedValueProperty); }
set { SetValue(BrowserStackDesktopOSSelectedValueProperty, value);
AvailableBrowserStackDesktopOSVersions = AvailableBrowserStackDesktopPlatforms.GetOSVersions(BrowserStackDesktopOSSelectedValue);
NotifyPropertyChanged("BrowserStackDesktopOSSelectedValue");
}
}
However when I select a value for the first ComboBox nothing happens. I am wanting the Itemsource of the next ComboBox to by populated.
What have I done wrong?
Basically you need to expose in your MVVM 2 collections of values for combo-box choices and two properties for selected values.
In the beginning only the first collection if filled with values. When the first selected value changes the second collection will be filled in with appropriate values. Here is an example implementation:
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Set the data context of the window
DataContext = new TestVM();
}
}
public class TestVM : INotifyPropertyChanged
{
#region Class attributes
protected static string[] firstComboValues = new string[] { "Choice_1", "Choice_2" };
protected static string[][] secondComboValues =
new string[][] {
new string[] { "value_1_1", "value_1_2", "value_1_3" },
new string[] { "value_2_1", "value_2_2", "value_2_3" }
};
#endregion
#region Public Properties
#region FirstSelectedValue
protected string m_FirstSelectedValue;
/// <summary>
///
/// </summary>
public string FirstSelectedValue
{
get { return m_FirstSelectedValue; }
set
{
if (m_FirstSelectedValue != value)
{
m_FirstSelectedValue = value;
UpdateSecondComboValues();
NotifyPropertyChanged("FirstSelectedValue");
}
}
}
#endregion
#region SecondSelectedValue
protected string m_SecondSelectedValue;
/// <summary>
///
/// </summary>
public string SecondSelectedValue
{
get { return m_SecondSelectedValue; }
set
{
if (m_SecondSelectedValue != value)
{
m_SecondSelectedValue = value;
NotifyPropertyChanged("SecondSelectedValue");
}
}
}
#endregion
#region FirstComboValues
protected ObservableCollection<string> m_FirstComboValues;
/// <summary>
///
/// </summary>
public ObservableCollection<string> FirstComboValues
{
get { return m_FirstComboValues; }
set
{
if (m_FirstComboValues != value)
{
m_FirstComboValues = value;
NotifyPropertyChanged("FirstComboValues");
}
}
}
#endregion
#region SecondComboValues
protected ObservableCollection<string> m_SecondComboValues;
/// <summary>
///
/// </summary>
public ObservableCollection<string> SecondComboValues
{
get { return m_SecondComboValues; }
set
{
if (m_SecondComboValues != value)
{
m_SecondComboValues = value;
NotifyPropertyChanged("SecondComboValues");
}
}
}
#endregion
#endregion
public TestVM()
{
FirstComboValues = new ObservableCollection<string>(firstComboValues);
}
/// <summary>
/// Update the collection of values for the second combo box
/// </summary>
protected void UpdateSecondComboValues()
{
int firstComboChoice;
for (firstComboChoice = 0; firstComboChoice < firstComboValues.Length; firstComboChoice++)
{
if (firstComboValues[firstComboChoice] == FirstSelectedValue)
break;
}
if (firstComboChoice == firstComboValues.Length)// just in case of a bug
SecondComboValues = null;
else
SecondComboValues = new ObservableCollection<string>(secondComboValues[firstComboChoice]);
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And the associated XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="window" x:Class="Testing1.MainWindow">
<Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Width=" 300">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="10"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox x:Name="FirstOne" ItemsSource="{Binding FirstComboValues}" SelectedItem="{Binding FirstSelectedValue, Mode=TwoWay}"/>
<ComboBox x:Name="SecondOne" ItemsSource="{Binding SecondComboValues}" SelectedItem="{Binding SecondSelectedValue, Mode=TwoWay}" Grid.Row="2"/>
</Grid>
</Grid>
</Window>
As you can see the SelectedValue properties of combo boxes are binded in TwoWay mode so when SelectedValue property of the combo box changes it changes the value on the VM side. And in FirstSelectedValue property setter UpdateSecondComboValues() method is called to update values for the second combo box.
EDIT:
It happens because you mixed both INotifPropertyChanged and DependencyObject. You should choose one of them. Usually you implement INotifyPropertyChanged in your VM and the code in the property setter will work.
If you inherit from DependencyObject however, you should not write any code in the setter/getter. It will never be called by the TwoWay binding. It will just call GetValue(...) internally. To be able to execute an action on DependencyProperty change you should declare it differently with a property changed handler:
#region BrowserStackDesktopOSSelectedValue
/// <summary>
/// BrowserStackDesktopOSSelectedValue Dependency Property
/// </summary>
public static readonly DependencyProperty BrowserStackDesktopOSSelectedValue Property =
DependencyProperty.Register("BrowserStackDesktopOSSelectedValue ", typeof(string), typeof(YourVM),
new FrameworkPropertyMetadata((string)null,
new PropertyChangedCallback(OnBrowserStackDesktopOSSelectedValue Changed)));
/// <summary>
/// Gets or sets the BrowserStackDesktopOSSelectedValue property. This dependency property
/// indicates ....
/// </summary>
public string BrowserStackDesktopOSSelectedValue
{
get { return (string)GetValue(BrowserStackDesktopOSSelectedValue Property); }
set { SetValue(BrowserStackDesktopOSSelectedValue Property, value); }
}
/// <summary>
/// Handles changes to the BrowserStackDesktopOSSelectedValue property.
/// </summary>
private static void OnBrowserStackDesktopOSSelectedValue Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
YourVM target = (YourVM)d;
string oldBrowserStackDesktopOSSelectedValue = (string)e.OldValue;
string newBrowserStackDesktopOSSelectedValue = target.BrowserStackDesktopOSSelectedValue ;
target.OnBrowserStackDesktopOSSelectedValue Changed(oldBrowserStackDesktopOSSelectedValue , newBrowserStackDesktopOSSelectedValue );
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the BrowserStackDesktopOSSelectedValue property.
/// </summary>
protected virtual void OnBrowserStackDesktopOSSelectedValue Changed(string oldBrowserStackDesktopOSSelectedValue , string newBrowserStackDesktopOSSelectedValue )
{
//Here write some code to update your second ComboBox content.
AvailableBrowserStackDesktopOSVersions = AvailableBrowserStackDesktopPlatforms.GetOSVersions(BrowserStackDesktopOSSelectedValue);
}
#endregion
By the way I always use Dr WPF snippets to write DPs so it goes much faster.
in general TextBox control TextChanged event is working but in ItemsControl, the TextBox TextChanged Event is not fired how can i do this. I trying to do by using following code which I have implemented but not getting result which I want.
So, what am I doing wrong?
View
<Window x:Class="SoniSoft.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ff="clr-namespace:SoniSoft"
Title="Window1" Height="300" Width="300">
<Window.DataContext>
<ff:ViewModels/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="38"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" ff:TextBoxBehaviour.TextChangedCommand="{Binding TextChanged}" />
<ItemsControl Margin="7,0,0,0" Grid.Row="3" ItemsSource="{Binding Path=ViewModelSearchResults}" x:Name="list">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Grid>
<TextBox ff:TextBoxBehaviour.TextChangedCommand="{Binding TextChanged}" Text="{Binding Path=CategoryName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="14" FontWeight="Normal" x:Name=" TextBoxCategoryName" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
View Models
class ViewModels :ViewModelBase
{
public ObservableCollection<Category> AllCategorys = new ObservableCollection<Category>();
DatabaseDataContext db = new DatabaseDataContext();
private ListCollectionView _objViewModelSearchResults;
public ListCollectionView ViewModelSearchResults
{
get { return _objViewModelSearchResults; }
set
{
_objViewModelSearchResults = value;
OnPropertyChanged("ViewModelSearchResults");
}
}
public ViewModels()
{
AllCategorys.Clear();
foreach (var item in db.Categories.OrderBy(c => c.CategoryName))
{
AllCategorys.Add(item);
}
ViewModelSearchResults = new ListCollectionView(AllCategorys);
}
public ICommand TextChanged
{
get
{
// this is very lazy: I should cache the command!
return new TextChangedCommand();
}
}
private class TextChangedCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Text Changed");
}
public bool CanExecute(object parameter)
{
return true;
}
}
}
DependencyProperty
class EventBehaviourFactory
{
public static DependencyProperty CreateCommandExecutionEventBehaviour(RoutedEvent routedEvent, string propertyName, Type ownerType)
{
DependencyProperty property = DependencyProperty.RegisterAttached(propertyName, typeof(ICommand), ownerType,
new PropertyMetadata(null,
new ExecuteCommandOnRoutedEventBehaviour(routedEvent).PropertyChangedHandler));
return property;
}
private class ExecuteCommandOnRoutedEventBehaviour : ExecuteCommandBehaviour
{
private readonly RoutedEvent _routedEvent;
public ExecuteCommandOnRoutedEventBehaviour(RoutedEvent routedEvent)
{
_routedEvent = routedEvent;
}
/// <summary>
/// Handles attaching or Detaching Event handlers when a Command is assigned or unassigned
/// </summary>
/// <param name="sender"></param>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
protected override void AdjustEventHandlers(DependencyObject sender, object oldValue, object newValue)
{
UIElement element = sender as UIElement;
if (element == null) { return; }
if (oldValue != null)
{
element.RemoveHandler(_routedEvent, new RoutedEventHandler(EventHandler));
}
if (newValue != null)
{
element.AddHandler(_routedEvent, new RoutedEventHandler(EventHandler));
}
}
protected void EventHandler(object sender, RoutedEventArgs e)
{
HandleEvent(sender, e);
}
}
internal abstract class ExecuteCommandBehaviour
{
protected DependencyProperty _property;
protected abstract void AdjustEventHandlers(DependencyObject sender, object oldValue, object newValue);
protected void HandleEvent(object sender, EventArgs e)
{
DependencyObject dp = sender as DependencyObject;
if (dp == null)
{
return;
}
ICommand command = dp.GetValue(_property) as ICommand;
if (command == null)
{
return;
}
if (command.CanExecute(e))
{
command.Execute(e);
}
}
/// <summary>
/// Listens for a change in the DependencyProperty that we are assigned to, and
/// adjusts the EventHandlers accordingly
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void PropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// the first time the property changes,
// make a note of which property we are supposed
// to be watching
if (_property == null)
{
_property = e.Property;
}
object oldValue = e.OldValue;
object newValue = e.NewValue;
AdjustEventHandlers(sender, oldValue, newValue);
}
}
}
class TextBoxBehaviour
{
public static readonly DependencyProperty TextChangedCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(TextBox.TextChangedEvent, "TextChangedCommand", typeof(TextBoxBehaviour));
public static void SetTextChangedCommand(DependencyObject o, ICommand value)
{
o.SetValue(TextChangedCommand, value);
}
public static ICommand GetTextChangedCommand(DependencyObject o)
{
return o.GetValue(TextChangedCommand) as ICommand;
}
}
Here is the problem. You are setting the command in an ItemTemplate. Thus it is binding to the Category object you have in the ListCollectionView. Now this is the object that doesnt contain any command for your text changed. What does contain the command for your TextChanged is the DataContext of the UserControl and you need to bind it to that.
Now there are is a way to work around and its called Ancestor RelativeSource. As I work with silverlight it might work different but this line of code should do.
Edit:
The actual line should be. this because it is ofcourse a window and you need to have the DataContext (the viewmodel) and then the property TextChanged:
<TextBox ff:TextBoxBehaviour.TextChangedCommand="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}, Path=DataContext.TextChanged}" />
I am throwing an ApplicationException if the FirstName value is null or empty and I am trying to display the error message in the TextBlock which is part of the ErrorTemplate. But it always shows "Exception has been thrown at the target of invocation".
public string FirstName
{
get { return _firstName;}
set
{
if(String.IsNullOrEmpty(value))
throw new ApplicationException("FirstName cannot be null or empty!");
_firstName = value;
OnPropertyChanged("FirstName");
}
}
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt"
Text="{Binding ElementName=MyAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And finally here is the TextBox control:
<TextBox Name="txtFirstName" Style="{StaticResource TextBoxStyle}" Grid.Column="1" Grid.Row="0" Height="20" Width="100" Margin="10">
<TextBox.Text>
<Binding Path="FirstName">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
I went through this the other day, I ended up taking the exception off the property and using a validation class.
<TextBox Name="txtFirstName" Style="{StaticResource TextBoxStyle}" Grid.Column="1" Grid.Row="0" Height="20" Width="100" Margin="10">
<TextBox.Text>
<Binding Path="FirstName" >
<Binding.ValidationRules>
<validators:StringRangeValidationRule
MinimumLength="1"
MaximumLength="40"
ErrorMessage="Required" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
Remove the exception from your property so it just looks like this ...
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
Heres the validation class (I poached this off the internet somewhere) ...
public class StringRangeValidationRule : ValidationRule
{
private int _minimumLength = -1;
private int _maximumLength = -1;
private string _errorMessage;
public int MinimumLength
{
get { return _minimumLength; }
set { _minimumLength = value; }
}
public int MaximumLength
{
get { return _maximumLength; }
set { _maximumLength = value; }
}
public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
ValidationResult result = new ValidationResult(true, null);
string inputString = (value ?? string.Empty).ToString();
if (inputString.Length < this.MinimumLength ||
(this.MaximumLength > 0 &&
inputString.Length > this.MaximumLength))
{
result = new ValidationResult(false, this.ErrorMessage);
}
return result;
}
}
You'll need to add a namespace reference in your xaml to where the validation class lives called validators (i'm sure you know this but just for completeness)
Something like ...
xmlns:validators="clr-namespace:WpfApplication1"
Hope this helps!
Cheers,
Andy
Again, I went through exactly the same thing this week! I found the following on the web, wrap your text boxes in this ...
<validators:ValidatedContent Name="Validator" >
<!-- All your textboxes here -->
</validators:ValidatedContent>
Add the class below in the same place you put the other one ... and then you can call Validator.Validate() from a button click or where ever you want. There is also an IsContentValid property you can use to decide whether you want to save etc.
public class ValidatedContent : Decorator
{
#region Public Constructors
/// <summary>
/// Initializes a new instance of ValidatedContent
/// </summary>
public ValidatedContent()
{
ErrorMessages = new ObservableCollection<string>();
Loaded += new RoutedEventHandler(OnValidatedContentLoaded);
}
#endregion
#region Event Handlers
/// <summary>
/// Handles the loaded event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnValidatedContentLoaded(object sender, RoutedEventArgs e)
{
Queue<DependencyObject> elementQueue = new Queue<DependencyObject>();
elementQueue.Enqueue(this.Child);
// Iterate over all the child elements
while (elementQueue.Count > 0)
{
// Dequeue the first element in the queue
DependencyObject element = elementQueue.Dequeue();
if (element != null)
{
foreach (var childElement in LogicalTreeHelper.GetChildren(element))
{
if (childElement is DependencyObject)
elementQueue.Enqueue((DependencyObject)childElement);
}
}
Control control = element as Control;
// Mark the element as valid if it is a control
if (control != null && GetIsRequired(element))
{
control.SetValue(Control.StyleProperty, RequiredControlStyle);
}
}
}
#endregion
#region Dependency Properties
public static readonly DependencyProperty IsContentValidProperty =
DependencyProperty.Register("IsContentValid", typeof(bool),
typeof(ValidatedContent), new UIPropertyMetadata(false));
public static readonly DependencyProperty ErrorMessagesProperty =
DependencyProperty.Register("ErrorMessages", typeof(ObservableCollection<string>),
typeof(ValidatedContent), new UIPropertyMetadata(null));
public static readonly DependencyProperty RequiredControlStyleProperty =
DependencyProperty.Register("RequiredControlStyle", typeof(Style),
typeof(ValidatedContent), new UIPropertyMetadata(null));
public static readonly DependencyProperty IsRequiredProperty =
DependencyProperty.RegisterAttached("IsRequired", typeof(bool),
typeof(ValidatedContent), new UIPropertyMetadata(false));
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the style to mark a required control
/// </summary>
public Style RequiredControlStyle
{
get { return (Style)GetValue(RequiredControlStyleProperty); }
set { SetValue(RequiredControlStyleProperty, value); }
}
/// <summary>
/// Gets or sets the error messages for the validated content
/// </summary>
public ObservableCollection<string> ErrorMessages
{
get { return (ObservableCollection<string>)GetValue(ErrorMessagesProperty); }
private set { SetValue(ErrorMessagesProperty, value); }
}
/// <summary>
/// Gets if the content is valid
/// </summary>
public bool IsContentValid
{
get { return (bool)GetValue(IsContentValidProperty); }
private set { SetValue(IsContentValidProperty, value); }
}
#endregion
#region Public Methods
/// <summary>
/// Validates the content of the decorator
/// </summary>
public void Validate()
{
IsContentValid = true;
ErrorMessages.Clear();
Queue<DependencyObject> elementQueue = new Queue<DependencyObject>();
elementQueue.Enqueue(this.Child);
// Iterate over all the child elements
while (elementQueue.Count > 0)
{
// Dequeue the first element in the queue
DependencyObject element = elementQueue.Dequeue();
foreach (var childElement in LogicalTreeHelper.GetChildren(element))
{
if (childElement is DependencyObject)
elementQueue.Enqueue((DependencyObject)childElement);
}
// Validate the bindings of the element
ValidateBindings(element);
}
}
#endregion
#region Private Methods
/// <summary>
/// Validates the bindings of the dependency object
/// </summary>
/// <param name="element"></param>
private void ValidateBindings(DependencyObject element)
{
if (element != null)
{
Type elementType = element.GetType();
FieldInfo[] dependencyPropertyFields = elementType.GetFields(
BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly);
// Iterate over all dependency properties
foreach (FieldInfo dependencyPropertyField in dependencyPropertyFields)
{
DependencyProperty dependencyProperty =
dependencyPropertyField.GetValue(element) as DependencyProperty;
if (dependencyProperty != null)
{
Binding binding = BindingOperations.GetBinding(element, dependencyProperty);
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(element, dependencyProperty);
// Issue 1822 - Extra check added to prevent null reference exceptions
if (binding != null && bindingExpression != null)
{
// Validate the validation rules of the binding
foreach (ValidationRule rule in binding.ValidationRules)
{
ValidationResult result = rule.Validate(element.GetValue(dependencyProperty),
CultureInfo.CurrentCulture);
bindingExpression.UpdateSource();
if (!result.IsValid)
{
ErrorMessages.Add(result.ErrorContent.ToString());
}
IsContentValid &= result.IsValid;
}
}
}
}
}
}
#endregion
#region Static Methods
/// <summary>
/// Gets the value for the IsRequired attached property
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static bool GetIsRequired(DependencyObject obj)
{
return (bool)obj.GetValue(IsRequiredProperty);
}
/// <summary>
/// Sets the value for the IsRequired attached property
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
public static void SetIsRequired(DependencyObject obj, bool value)
{
obj.SetValue(IsRequiredProperty, value);
}
#endregion
}
I had some issues with this too so I am posting my workaround in case it would help someone.
The message shown in the Text block in not the correct one because the Exception that is triggered in the code is wrapped up into a TargetInvocationException. So the error message displayed is the one of that exception which is "Exception has been thrown at the target of invocation".
The exception you want to display is actually accessible in the InnerException of the TargetInvocationException and you can therefore display it message using the following statement in your XAML
Text="{Binding ElementName=MyAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent.InnerException.Message}"
I am pretty sure there must be a way that the error contained in the ErrorContent field is the correct one but I have not digged enough to understand how.