summary of soInput.xaml:
xmlns:core="clr-namespace:MyProject.Fun.Core"
xmlns:behave="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<TextBox Name="txtValue"
Margin="0"
Style="{DynamicResource soTextBox}"
TextWrapping="NoWrap"
Height="50"
Foreground="#FF363636"
FontSize="18.667"
FontWeight="Bold"
Grid.ColumnSpan="2"
IsEnabled="{Binding IsEnabled,ElementName=Input}"
Text="{Binding soValue,ElementName=Input,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
GotFocus="txtValue_GotFocus"
LostFocus="txtValue_LostFocus">
<behave:Interaction.Behaviors>
<core:TextBoxKeyboardBehavior/>
</behave:Interaction.Behaviors>
</TextBox>
soInput.xaml.cs:
namespace MyProject.Fun
{
using System;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
public partial class soInput : UserControl, IComponentConnector
{
public static readonly DependencyProperty soErrorProperty = DependencyProperty.Register("soError", typeof(string), typeof(soInput));
public static readonly DependencyProperty soHelpDescriptionProperty = DependencyProperty.Register("soHelpDescription", typeof(string), typeof(soInput), new UIPropertyMetadata(""));
public static readonly DependencyProperty soHelpTitleProperty = DependencyProperty.Register("soHelpTitle", typeof(string), typeof(soInput), new UIPropertyMetadata(""));
public static readonly DependencyProperty soLabelProperty = DependencyProperty.Register("soLabel", typeof(string), typeof(soInput));
public static readonly DependencyProperty soValueProperty = DependencyProperty.Register("soValue", typeof(string), typeof(soInput));
public soInput()
{
this.InitializeComponent();
}
private void Input_GotFocus(object sender, RoutedEventArgs e)
{
}
private void txtValue_GotFocus(object sender, RoutedEventArgs e)
{
}
private void txtValue_LostFocus(object sender, RoutedEventArgs e)
{
}
public string soError
{
get
{
return (string) base.GetValue(soErrorProperty);
}
set
{
base.SetValue(soErrorProperty, value);
}
}
public string soHelpDescription
{
get
{
return (string) base.GetValue(soHelpDescriptionProperty);
}
set
{
base.SetValue(soHelpDescriptionProperty, value);
}
}
public string soHelpTitle
{
get
{
return (string) base.GetValue(soHelpTitleProperty);
}
set
{
base.SetValue(soHelpTitleProperty, value);
}
}
public string soLabel
{
get
{
return (string) base.GetValue(soLabelProperty);
}
set
{
base.SetValue(soLabelProperty, value);
}
}
public string soValue
{
get
{
return (string) base.GetValue(soValueProperty);
}
set
{
base.SetValue(soValueProperty, value);
}
}
}
}
TextBoxKeyboardBehavior.cs:
using MyProject.Fun.Helpers;
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
namespace MyProject.Fun.Core
{
public partial class TextBoxKeyboardBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
base.AssociatedObject.GotFocus += new RoutedEventHandler(this.AssociatedObject_GotFocus);
base.AssociatedObject.LostFocus += new RoutedEventHandler(this.AssociatedObject_LostFocus);
}
private void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
{
Button button = base.AssociatedObject.Template.FindName("keyboardBtn", base.AssociatedObject) as Button;
if (button != null)
{
button.Click -= new RoutedEventHandler(this.btn_Click);
}
if (button != null)
{
button.Visibility = Visibility.Hidden;
}
}
private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
{
Button button = base.AssociatedObject.Template.FindName("keyboardBtn", base.AssociatedObject) as Button;
if (button == null)
{
base.AssociatedObject.ApplyTemplate();
}
button = (base.AssociatedObject.Template.FindName("keyboardBtn", base.AssociatedObject) as Button);
if (button != null)
{
button.Click += new RoutedEventHandler(this.btn_Click);
button.Visibility = Visibility.Visible;
}
}
private void btn_Click(object sender, RoutedEventArgs e)
{
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(base.AssociatedObject, TextBox.TextProperty);
Keyboard keyboard = new Keyboard();
Binding binding = new Binding();
binding.Source = bindingExpression.DataItem;
binding.Path = bindingExpression.ParentBinding.Path;
binding.Mode = bindingExpression.ParentBinding.Mode;
binding.UpdateSourceTrigger = bindingExpression.ParentBinding.UpdateSourceTrigger;
binding.NotifyOnTargetUpdated = bindingExpression.ParentBinding.NotifyOnTargetUpdated;
binding.NotifyOnSourceUpdated = bindingExpression.ParentBinding.NotifyOnSourceUpdated;
binding.NotifyOnValidationError = bindingExpression.ParentBinding.NotifyOnValidationError;
binding.ValidatesOnDataErrors = bindingExpression.ParentBinding.ValidatesOnDataErrors;
keyboard.TextTxt.SetBinding(TextBox.TextProperty, binding);
bool visibility = NavigationHelper.MainLayout.ModalBackground.Visibility != Visibility.Visible;
Util.ShowKeyboardForm(keyboard, visibility);
}
private object GetValue(string path, object dataItem)
{
string[] array = path.Split(new char[]
{
'.'
});
object obj = null;
System.Reflection.PropertyInfo property = dataItem.GetType().GetProperty(path);
object result;
if (property != null)
{
obj = property.GetValue(dataItem, null);
result = obj;
}
else
{
string[] array2 = array;
for (int i = 0; i < array2.Length; i++)
{
string name = array2[i];
if (obj == null)
{
property = dataItem.GetType().GetProperty(name);
obj = property.GetValue(dataItem, null);
}
else
{
property = obj.GetType().GetProperty(name);
obj = property.GetValue(obj, null);
}
}
result = obj;
}
return result;
}
}
}
error:
The name "TextBoxKeyboardBehavior" does not exist in the namespace "clr-namespace:MyProject.Fun.Core".
in backup of project it does work but in this case does not work !!!
all code are in same project and work last night but today does not work !
Related
I have a custom combobox in my WPF application and the combo box is not updating when my item source is updated after initial launch. The itemsSource is a custom observableDictionary. I've implemented INotifyPropertyChanged on my properties with my observableDictionary but it won't update. I've searched through every WPF propertychanged event not working on stack overflow and i'm looking for some assistance.
Custom Combobox Code:
<controls:MultiSelectComboBox Width="100" Height="30" Padding="0 10 0 0" Grid.Row="0" Grid.Column="1"
ItemsSource="{Binding Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True,
Path=DataContext.OptionTeamMembersDictionary,
BindsDirectlyToSource=True,
RelativeSource={RelativeSource AncestorType={x:Type UserControl},Mode=FindAncestor}}"
SelectedItems="{Binding SelectedOptionTeamMembersDictionary, Mode=TwoWay}"
x:Name="TeamMemberDisplay"
ToolTip="{Binding Path=Text, RelativeSource={RelativeSource Self}}"/>
Properties and private variables:
private ObservableDictionary<string, object> _optionTeamMembersDictionary;
private ObservableDictionary<string, object> _selectedOptionTeamMembersDictionary;
public ObservableDictionary<string, object> OptionTeamMembersDictionary
{
get
{
return _optionTeamMembersDictionary;
}
set
{
_optionTeamMembersDictionary = value;
OnPropertyChanged("OptionTeamMembersDictionary");
}
}
public ObservableDictionary<string, object> SelectedOptionTeamMembersDictionary
{
get
{
return _selectedOptionTeamMembersDictionary;
}
set
{
_selectedOptionTeamMembersDictionary = value;
OnPropertyChanged("SelectedOptionTeamMembersDictionary");
}
}
I use a button to trigger a database pull which leads each row into an object and then populates the optionTeamMemberDictionary when it returns more then one row of data.
All of the above works when the data is loaded in my constructor but when its loaded after launch my comboboxes do not show the new data in the collection.
EDIT:
Code for the Custom ComboBox being used:
https://www.codeproject.com/Articles/563862/Multi-Select-ComboBox-in-WPF is the URL this came from. Some edits were made to implement an obserableDirctionary instead of normal dictionary
public partial class MultiSelectComboBox : UserControl
{
private ObservableCollection<Node> _nodeList;
public MultiSelectComboBox()
{
InitializeComponent();
_nodeList = new ObservableCollection<Node>();
}
#region Dependency Properties
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(ObservableDictionary<string, object>), typeof(MultiSelectComboBox), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MultiSelectComboBox.OnItemsSourceChanged)));
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(ObservableDictionary<string, object>), typeof(MultiSelectComboBox), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MultiSelectComboBox.OnSelectedItemsChanged)));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MultiSelectComboBox), new UIPropertyMetadata(string.Empty));
public static readonly DependencyProperty DefaultTextProperty =
DependencyProperty.Register("DefaultText", typeof(string), typeof(MultiSelectComboBox), new UIPropertyMetadata(string.Empty));
public ObservableDictionary<string, object> ItemsSource
{
get { return (ObservableDictionary<string, object>)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
}
}
public ObservableDictionary<string, object> SelectedItems
{
get { return (ObservableDictionary<string, object>)GetValue(SelectedItemsProperty); }
set
{
SetValue(SelectedItemsProperty, value);
}
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public string DefaultText
{
get { return (string)GetValue(DefaultTextProperty); }
set { SetValue(DefaultTextProperty, value); }
}
#endregion
#region Events
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox control = (MultiSelectComboBox)d;
control.DisplayInControl();
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox control = (MultiSelectComboBox)d;
control.SelectNodes();
control.SetText();
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox clickedBox = (CheckBox)sender;
if (clickedBox.Content == "All")
{
if (clickedBox.IsChecked.Value)
{
foreach (Node node in _nodeList)
{
node.IsSelected = true;
}
}
else
{
foreach (Node node in _nodeList)
{
node.IsSelected = false;
}
}
}
else
{
int _selectedCount = 0;
foreach (Node s in _nodeList)
{
if (s.IsSelected && s.Title != "All")
_selectedCount++;
}
if (_selectedCount == _nodeList.Count - 1)
_nodeList.FirstOrDefault(i => i.Title == "All").IsSelected = true;
else
_nodeList.FirstOrDefault(i => i.Title == "All").IsSelected = false;
}
SetSelectedItems();
SetText();
}
#endregion
#region Methods
private void SelectNodes()
{
foreach (KeyValuePair<string, object> keyValue in SelectedItems)
{
Node node = _nodeList.FirstOrDefault(i => i.Title == keyValue.Key);
if (node != null)
node.IsSelected = true;
}
}
private void SetSelectedItems()
{
if (SelectedItems == null)
SelectedItems = new ObservableDictionary<string, object>();
SelectedItems.Clear();
foreach (Node node in _nodeList)
{
if (node.IsSelected && node.Title != "All")
{
if (this.ItemsSource.Count > 0)
SelectedItems.Add(node.Title, this.ItemsSource[node.Title]);
}
}
}
private void DisplayInControl()
{
_nodeList.Clear();
if (this.ItemsSource.Count > 0)
_nodeList.Add(new Node("All"));
foreach (KeyValuePair<string, object> keyValue in this.ItemsSource)
{
Node node = new Node(keyValue.Key);
_nodeList.Add(node);
}
MultiSelectCombo.ItemsSource = _nodeList;
}
private void SetText()
{
if (this.SelectedItems != null)
{
StringBuilder displayText = new StringBuilder();
foreach (Node s in _nodeList)
{
if (s.IsSelected == true && s.Title == "All")
{
displayText = new StringBuilder();
displayText.Append("All");
break;
}
else if (s.IsSelected == true && s.Title != "All")
{
displayText.Append(s.Title);
displayText.Append(',');
}
}
this.Text = displayText.ToString().TrimEnd(new char[] { ',' });
}
// set DefaultText if nothing else selected
if (string.IsNullOrEmpty(this.Text))
{
this.Text = this.DefaultText;
}
}
#endregion
}
public class Node : INotifyPropertyChanged
{
private string _title;
private bool _isSelected;
#region ctor
public Node(string title)
{
Title = title;
}
#endregion
#region Properties
public string Title
{
get
{
return _title;
}
set
{
_title = value;
NotifyPropertyChanged("Title");
}
}
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
After much troubleshooting i was able to figure out an answer for this. onItemsSourceChanged was not firing after the control was instantiated so it never got updates made after initial launch of the application. I edited the OnItemsSourceChanged Function on the MultiFunctionComboBox to the below so it would fire on a changed event and it is working as expected now.
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox control = (MultiSelectComboBox)d;
var action = new NotifyCollectionChangedEventHandler(
(o, args) =>
{
MultiSelectComboBox c = (MultiSelectComboBox)d;
c?.DisplayInControl();
});
if (e.OldValue != null)
{
var sourceCollection = (ObservableDictionary<string, object>)e.OldValue;
sourceCollection.CollectionChanged -= action;
}
if (e.NewValue != null)
{
var sourceCollection = (ObservableDictionary<string, object>)e.NewValue;
sourceCollection.CollectionChanged += action;
}
control.DisplayInControl();
}
I use TestStack White framework to automate testing of a WPF Application
It needs to open modal window and access TextBox in it. Everything works well, but White can't find Textbox, although it finds other elements of window
I tried the following lines of code:
TestStack.White.UIItems.TextBox TextBox = CreateBranch.Get<TestStack.White.UIItems.TextBox>(SearchCriteria.byAutomationId("Title"));
where CreateBranch is modal window
I also tried (SearchCriteria.All), (SearchCriteria.ByControlType) and nothing works
Coded UI tool finds this element well by AutomationID, but I need to do it in White
UISpy and other similar tools recognize this control and see its AutomationID
This textbox is custom control, here's code for it, I changed namespace name for privacy:
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace Test.Wpf.Controls.XTextBox
{
[TemplatePart(Name = "PART_Watermark", Type = typeof(TextBlock))]
[TemplatePart(Name = "PART_Pasword", Type = typeof(TextBlock))]
public class XTextBox : TextBox
{
#region Static
static XTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(XTextBox), new FrameworkPropertyMetadata(typeof(XTextBox)));
}
#endregion //Static
#region Fields
private TextBlock PART_Watermark;
private TextBlock PART_Pasword;
#endregion //Fields
#region DependencyProperties
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(
"Watermark",
typeof(String),
typeof(XTextBox),
new PropertyMetadata(String.Empty));
public static readonly DependencyProperty WatermarkVerticalAlignmentProperty = DependencyProperty.Register(
"WatermarkVerticalAlignment",
typeof(VerticalAlignment),
typeof(XTextBox),
new PropertyMetadata(VerticalAlignment.Stretch));
public static readonly DependencyProperty WatermarkForegroundProperty = DependencyProperty.Register(
"WatermarkForeground",
typeof(Brush),
typeof(XTextBox),
new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public static readonly DependencyProperty WatermarkFontSizeProperty = DependencyProperty.Register(
"WatermarkFontSize",
typeof(Double),
typeof(XTextBox),
new PropertyMetadata(12.0));
public static readonly DependencyProperty IsFloatingProperty = DependencyProperty.Register(
"IsFloating",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty IsAccessNegativeProperty = DependencyProperty.Register(
"IsAccessNegative",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(true));
public static readonly DependencyProperty IsDigitOnlyProperty = DependencyProperty.Register(
"IsDigitOnly",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(
"MaxValue",
typeof(Single),
typeof(XTextBox),
new PropertyMetadata(Single.NaN));
public static readonly DependencyProperty IsPasswordProperty = DependencyProperty.Register(
"IsPassword",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty VisibilityMainTextProperty = DependencyProperty.Register(
"VisibilityMainText",
typeof(Visibility),
typeof(XTextBox),
new PropertyMetadata(Visibility.Visible));
#endregion //DependencyProperties
#region Properties
[Description("Gets or sets the watermark title")]
public String Watermark
{
get { return (String)GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
[Description("Gets or sets the watermark vertical alignment")]
public VerticalAlignment WatermarkVerticalAlignment
{
get { return (VerticalAlignment)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the watermark title color")]
public Brush WatermarkForeground
{
get { return (Brush)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the watermark title font size")]
public Double WatermarkFontSize
{
get { return (Double)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the textbox floating mode")]
public Boolean IsFloating
{
get { return (Boolean)GetValue(IsFloatingProperty); }
set { SetValue(IsFloatingProperty, value); }
}
[Description("Gets or sets the textbox access of negative values")]
public Boolean IsAccessNegative
{
get { return (Boolean)GetValue(IsAccessNegativeProperty); }
set { SetValue(IsAccessNegativeProperty, value); }
}
[Description("Gets or sets the textbox chars type")]
public Boolean IsDigitOnly
{
get { return (Boolean)GetValue(IsDigitOnlyProperty); }
set { SetValue(IsDigitOnlyProperty, value); }
}
[Description("Gets or sets the max input value (enable in digit mode only)")]
public Single MaxValue
{
get { return (Single)GetValue(MaxValueProperty); }
set { SetValue(MaxValueProperty, value); }
}
[Description("Gets or sets the textbox is passwordbox")]
public Boolean IsPassword
{
get { return (Boolean)GetValue(IsPasswordProperty); }
set { SetValue(IsPasswordProperty, value); }
}
public Visibility VisibilityMainText
{
get { return (Visibility)GetValue(VisibilityMainTextProperty); }
set { SetValue(VisibilityMainTextProperty, value); }
}
#endregion //Properties
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
PART_Watermark = GetTemplateChild("PART_Watermark") as TextBlock;
PART_Pasword = GetTemplateChild("PART_Pasword") as TextBlock;
SetWatermarkVisibility();
if (IsPassword)
{
VisibilityMainText = Visibility.Collapsed;
if (PART_Pasword != null)
{
PART_Pasword.Visibility = Visibility.Visible;
PART_Pasword.FontSize = 20;
}
}
else
{
VisibilityMainText = Visibility.Visible;
}
DataObject.AddPastingHandler(this, OnPaste);
}
protected void OnPaste(Object sender, DataObjectPastingEventArgs e)
{
try
{
var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
if (!isText) return;
var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as String;
if (!String.IsNullOrEmpty(text))
{
if (IsDigitOnly)
{
if (!IsAccessNegative)
{
var ch = text[0];
if (ch == 45)
{
e.CancelCommand();
}
}
for (int i = 0; i < text.Length; i++)
{
if (i == 0)
{
if (IsAccessNegative && text[i] == 45)
{
continue;
}
}
if (!Char.IsDigit(text[0]))
e.CancelCommand();
}
}
}
}
catch (Exception)
{
// ignored
e.Handled = true;
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
SetWatermarkVisibility();
if (IsPassword)
{
PART_Pasword.Text = new String('•', Text.Length);
}
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
SetWatermarkVisibility();
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
if (PART_Watermark != null)
{
PART_Watermark.Visibility = Visibility.Hidden;
}
}
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
base.OnPreviewTextInput(e);
if (e.Text.Length == 0)
{
e.Handled = true;
return;
}
if (IsDigitOnly)
{
var ch = e.Text[0];
if (!Char.IsDigit(ch) && ch != 8 && ch != 46)
{
if (!(IsAccessNegative && ch == 45))
e.Handled = true;
}
if (IsFloating)
{
if (ch == 46 && Text.IndexOf('.') != -1)
{
e.Handled = true;
return;
}
}
if (!IsAccessNegative)
{
if (ch == 45)
{
e.Handled = true;
}
}
}
}
#region Private
private void SetWatermarkVisibility()
{
if (PART_Watermark != null)
{
PART_Watermark.Visibility = (Text != String.Empty || IsKeyboardFocused)? Visibility.Hidden : Visibility.Visible;
}
}
#endregion
}
}
Screenshot from UISpy
Let me know if that works
TextBox = (TextBox)CreateBranch
.Get(SearchCriteria.ByAutomationId("Title").AndOfFramework(WindowsFramework.Wpf));
Edited after new source added
You have to create a specific AutomationPeer for your custom control and return it via the override of the method OnCreateAutomationPeer().
Your control is a subclass of the TextBox control so you can just return a new TextBoxAutomationPeer instance or create your custom AutomationPeer from it.
public class XTextBox : TextBox
{
...
protected override AutomationPeer OnCreateAutomationPeer()
{
return new XTextBoxAutomationPeer(this);
// or just use the TextBoxAutomationPeer
// return new TextBoxAutomationPeer(this);
}
...
}
The custom automation peer
public class XTextBoxAutomationPeer : TextBoxAutomationPeer
{
public XTextBoxAutomationPeer(XTextBox owner)
: base(owner)
{
}
protected override string GetClassNameCore()
{
return "XTextBox";
}
}
[SetUpFixture]
public class SETUP_THAT_WILL_GET_CALL_LATER
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
var applicationDirectory = TestContext.CurrentContext.TestDirectory;
var applicationPath = Path.Combine(applicationDirectory, #"..\..\..\, "your debug folder path here", "your application.exe here");
Application = Application.Launch(applicationPath);
Thread.Sleep(2000);
Window = Application.GetWindow("Title of your application", InitializeOption.WithCache);
}
[OneTimeTearDown()]
public void OneTimeTearDown()
{
Window.Dispose();
Application.Dispose();
}
public static Application Application;
public static Window Window;
}
Then in your test
[Test]
public void yourtest()
{
var textBox = SETUP_THAT_WILL_GET_CALL_LATER.**Window.Get<TextBox>("Your textbox name here");**
}
in my WPF application I have several forms with a multitude of input fields for the user. As it turns out not every user needs all fields depending on its companie's process therefore I have the new requirement to allow the user to hide fields depending on its own needs.
I was planning to use a Behavior for this which could be attached to basically every WPF-Control. The behavior would add a ContextMenu to each of the controls allowing to show/hide all of the available Fields. My current test project which seems to work nice has four DependencyProperties to make everything work:
string VisibilityGroupName:
This acts somehow as an Id for each field but is not unique in order to group multiple fields togther (eG a Label for a field caption to its corresponding TextBox). This string is currently also used as the name that the user sees in the ContextMenu.
Dictionary VisibilityDictionary:
This dictionary keeps track of all the visibility states of the fields. In my application I'm going to serialize this to XML in order to make the users decisions persistant.
bool AllowCustomVisibility:
This is just a flag in order to switch the whole funtionality off.
bool NotificationDummy:
This is where it gets interesting. Currently I abuse this property in conjunction with an ValueChanged-event to notify all the Controls that a state was changed so they can check if they are affected. While this works as expected I know that this is just a bad workaround because I don't know how the notification would be done correctly.
Does anybody have an idea how it is done correctly? I've marked the corresponding places in the code with TODOs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace CustomizableUserControlVisibility
{
public class WPFCustomVisibilityBehavior : Behavior<DependencyObject>
{
#region Fields
private Control _control = null;
private ContextMenu _contextMenu;
private bool _contextMenuIsBuilt = false;
#endregion
#region Properties
public bool NotificationDummy
{
get { return (bool)GetValue(NotificationDummyProperty); }
set { SetValue(NotificationDummyProperty, value); }
}
public static readonly DependencyProperty NotificationDummyProperty = DependencyProperty.Register("NotificationDummy", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));
public bool AllowCustomVisibility
{
get { return (bool)GetValue(AllowCustomVisibilityProperty); }
set { SetValue(AllowCustomVisibilityProperty, value); }
}
public static readonly DependencyProperty AllowCustomVisibilityProperty = DependencyProperty.Register("AllowCustomVisibility", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));
public string VisibilityGroupName
{
get { return (string)GetValue(VisibilityGroupNameProperty); }
set { SetValue(VisibilityGroupNameProperty, value); }
}
public static readonly DependencyProperty VisibilityGroupNameProperty = DependencyProperty.Register("VisibilityGroupName", typeof(string), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(string.Empty));
public Dictionary<string, bool> VisibilityDictionary
{
get { return (Dictionary<string, bool>)GetValue(VisibilityDictionaryProperty); }
set { SetValue(VisibilityDictionaryProperty, value); }
}
public static readonly DependencyProperty VisibilityDictionaryProperty = DependencyProperty.Register("VisibilityDictionary", typeof(Dictionary<string, bool>), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(new Dictionary<string, bool>()));
#endregion
#region Constructor
public WPFCustomVisibilityBehavior()
{
// TODO: There should be a better way to notify other controls about state changes than this...
var temp = DependencyPropertyDescriptor.FromProperty(WPFCustomVisibilityBehavior.NotificationDummyProperty, typeof(WPFCustomVisibilityBehavior));
if (temp != null)
{
temp.AddValueChanged(this, OnNotificationDummyChanged);
}
}
#endregion
#region Overrrides
protected override void OnAttached()
{
base.OnAttached();
if (this.AllowCustomVisibility == false)
{
return;
}
this._control = this.AssociatedObject as Control;
if (!string.IsNullOrEmpty(this.VisibilityGroupName) && this._control != null)
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
else
{
this.VisibilityDictionary.Add(this.VisibilityGroupName, this._control.Visibility == Visibility.Visible ? true : false);
}
// Add a ContextMenu to the Control, but only if it does not already have one (TextBox brings its default ContextMenu for copy, cut and paste)
if (this._control.ContextMenu == null && !(this._control is TextBox))
{
this._contextMenu = new ContextMenu();
ContextMenuService.SetContextMenu(this._control, this._contextMenu);
this._control.ContextMenuOpening += (sender, e) => { ContextMenuOpening(e); };
}
}
}
#endregion
#region Event handling
private void ContextMenuOpening(ContextMenuEventArgs e)
{
if (this._contextMenuIsBuilt == false)
{
this._contextMenu.Items.Clear();
Dictionary<string, MenuItem> menuItems = new Dictionary<string, MenuItem>();
foreach (string k in this.VisibilityDictionary.Keys)
{
MenuItem menuItem = new MenuItem() { Header = k, Name = k, IsCheckable = true, StaysOpenOnClick = true };
menuItem.Click += MenuItem_Click;
menuItems.Add(k, menuItem);
}
var keyList = menuItems.Keys.ToList();
keyList.Sort();
foreach (string key in keyList)
{
this._contextMenu.Items.Add(menuItems[key]);
}
this._contextMenuIsBuilt = true;
}
foreach (MenuItem mi in this._contextMenu.Items)
{
mi.IsChecked = this.VisibilityDictionary[mi.Name];
}
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null && this.VisibilityDictionary != null && this.VisibilityDictionary.ContainsKey(mi.Name))
{
this.VisibilityDictionary[mi.Name] = mi.IsChecked;
// TODO: There should be a better way to notify other controls about state changes than this...
this.NotificationDummy = !NotificationDummy;
}
}
private void OnNotificationDummyChanged(object sender, EventArgs args)
{
// TODO: There should be a better way to notify other controls about state changes than this...
if (this._control != null && this.VisibilityDictionary != null && !string.IsNullOrEmpty(this.VisibilityGroupName))
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
}
}
#endregion
}
}
Due to the lack of any other ideas I decided to use a static Event which seems to solve my problem quite well and this approach at leasts saves me the NotificationDummy-DependencyProperty which I had to use in the first place.
If anybody is interested - here is my final solution:
namespace CustomizableUserControlVisibility
{
public delegate void VisibilityChangedEventHandler(object visibilityDictionary);
public class WPFCustomVisibilityBehavior : Behavior<DependencyObject>
{
#region Fields
public static event VisibilityChangedEventHandler OnVisibilityChanged;
private Control _control = null;
private ContextMenu _contextMenu;
private bool _contextMenuIsBuilt = false;
#endregion
#region Properties
public bool AllowCustomVisibility
{
get { return (bool)GetValue(AllowCustomVisibilityProperty); }
set { SetValue(AllowCustomVisibilityProperty, value); }
}
public static readonly DependencyProperty AllowCustomVisibilityProperty = DependencyProperty.Register("AllowCustomVisibility", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));
public string VisibilityGroupName
{
get { return (string)GetValue(VisibilityGroupNameProperty); }
set { SetValue(VisibilityGroupNameProperty, value); }
}
public static readonly DependencyProperty VisibilityGroupNameProperty = DependencyProperty.Register("VisibilityGroupName", typeof(string), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(string.Empty));
public Dictionary<string, bool> VisibilityDictionary
{
get { return (Dictionary<string, bool>)GetValue(VisibilityDictionaryProperty); }
set { SetValue(VisibilityDictionaryProperty, value); }
}
public static readonly DependencyProperty VisibilityDictionaryProperty = DependencyProperty.Register("VisibilityDictionary", typeof(Dictionary<string, bool>), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(new Dictionary<string, bool>()));
#endregion
#region Constructor
public WPFCustomVisibilityBehavior()
{
OnVisibilityChanged += VisibilityChanged;
}
#endregion
#region Overrrides
protected override void OnAttached()
{
base.OnAttached();
if (this.AllowCustomVisibility == false)
{
return;
}
this._control = this.AssociatedObject as Control;
if (!string.IsNullOrEmpty(this.VisibilityGroupName) && this._control != null)
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
else
{
this.VisibilityDictionary.Add(this.VisibilityGroupName, this._control.Visibility == Visibility.Visible ? true : false);
}
}
// Add a ContextMenu to the Control, but only if it does not already have one (TextBox brings its default ContextMenu for copy, cut and paste)
if (this._control != null && this._control.ContextMenu == null && !(this._control is TextBox))
{
this._contextMenu = new ContextMenu();
ContextMenuService.SetContextMenu(this._control, this._contextMenu);
this._control.ContextMenuOpening += (sender, e) => { ContextMenuOpening(e); };
}
}
#endregion
#region Event handling
private void ContextMenuOpening(ContextMenuEventArgs e)
{
if (this._contextMenuIsBuilt == false)
{
// Clear Items just to be sure there is nothing in it...
this._contextMenu.Items.Clear();
// Create default items first
MenuItem showAll = new MenuItem() { Header = "Show all optional fields", IsCheckable = false, FontWeight = FontWeights.Bold };
showAll.Click += MenuItem_ShowAll_Click;
MenuItem hideAll = new MenuItem() { Header = "Hide all optional fields", IsCheckable = false, FontWeight = FontWeights.Bold };
hideAll.Click += MenuItem_HideAll_Click;
// Create field items and sort them by name
Dictionary<string, MenuItem> menuItems = new Dictionary<string, MenuItem>();
foreach (string k in this.VisibilityDictionary.Keys)
{
MenuItem menuItem = new MenuItem() { Header = k, Name = k, IsCheckable = true, StaysOpenOnClick = true };
menuItem.Click += MenuItem_Click;
menuItems.Add(k, menuItem);
}
var keyList = menuItems.Keys.ToList();
keyList.Sort();
// Now add default items followed by field items
this._contextMenu.Items.Add(showAll);
this._contextMenu.Items.Add(hideAll);
this._contextMenu.Items.Add(new Separator());
foreach (string key in keyList)
{
this._contextMenu.Items.Add(menuItems[key]);
}
this._contextMenuIsBuilt = true;
}
foreach (Object o in this._contextMenu.Items)
{
MenuItem mi = o as MenuItem;
if (mi != null && mi.FontWeight != FontWeights.Bold)
{
mi.IsChecked = this.VisibilityDictionary[mi.Name];
}
}
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null && this.VisibilityDictionary != null && this.VisibilityDictionary.ContainsKey(mi.Name))
{
this.VisibilityDictionary[mi.Name] = mi.IsChecked;
OnVisibilityChanged(this.VisibilityDictionary);
}
}
private void MenuItem_HideAll_Click(object sender, RoutedEventArgs e)
{
List<string> keys = this.VisibilityDictionary.Keys.ToList<string>();
foreach (string key in keys)
{
this.VisibilityDictionary[key] = false;
}
OnVisibilityChanged(this.VisibilityDictionary);
}
private void MenuItem_ShowAll_Click(object sender, RoutedEventArgs e)
{
List<string> keys = this.VisibilityDictionary.Keys.ToList<string>();
foreach (string key in keys)
{
this.VisibilityDictionary[key] = true;
}
OnVisibilityChanged(this.VisibilityDictionary);
}
private void VisibilityChanged(object visibilityDictionary)
{
if (this._control != null && this.VisibilityDictionary != null && this.VisibilityDictionary == visibilityDictionary && !string.IsNullOrEmpty(this.VisibilityGroupName))
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
}
}
#endregion
}
}
I have read several posts on this topic but many are from a previous versions of VS or framework. What I am trying to do is selected multiple rows from a dataGrid and return those rows into a bound observable collection.
I have tried creating a property(of type) and adding it to an observable collection and it works with single records but the code never fires with multiple records.
Is there a clean way to do this in VS2013 using an MVVM patern?
Any thoughts would be appreciated.
<DataGrid x:Name="MainDataGrid" Height="390" Width="720"
VerticalAlignment="Center" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False"
ItemsSource="{Binding Path=DisplayInDataGrid}"
SelectedItem="{Binding Path=DataGridItemSelected}"
SelectionMode="Extended"
private ObservableCollection<ScannedItem> _dataGridItemsSelected;
public ObservableCollection<ScannedItem> DataGridItemsSelected
{
get { return _dataGridItemsSelected; }
set
{
_dataGridItemsSelected = value;
OnPropertyChanged("DataGridItemsSelected");
}
}
private ScannedItem _dataGridItemSelected;
public ScannedItem DataGridItemSelected
{
get { return _dataGridItemSelected;}
set
{
_dataGridItemSelected = value;
OnPropertyChanged("DataGridItemSelected");
EnableButtons();
LoadSelectedCollection(DataGridItemSelected);
}
}
void LoadSelectedCollection(ScannedItem si)
{
if (DataGridItemsSelected == null)
{
DataGridItemsSelected = new ObservableCollection<ScannedItem>();
}
DataGridItemsSelected.Add(si);
}
I have implemented a two-way data binding for MultiSelector.SelectedItems property using attached behavior pattern.
The following image shows how it works:
There are two DataGrids bound to the same model and they share selected items. Left DataGrid is active so selected items are blue and right DataGrid is inactive so selected items are gray.
Following is the sample code how to use it:
MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding People}" local:MultiSelectorExtension.SelectedItems="{Binding SelectedPeople}" CanUserAddRows="True"/>
<DataGrid Grid.Column="1" ItemsSource="{Binding People}" local:MultiSelectorExtension.SelectedItems="{Binding SelectedPeople}" CanUserAddRows="True"/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2">
<Button DockPanel.Dock="Top" Content="Select All" Command="{Binding SelectAllCommand}"/>
<Button DockPanel.Dock="Top" Content="Unselect All" Command="{Binding UnselectAllCommand}"/>
<Button DockPanel.Dock="Top" Content="Select Next Range" Command="{Binding SelectNextRangeCommand}"/>
</StackPanel>
</Grid>
</Window>
Model.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;
namespace WpfApplication
{
abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void Set<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
sealed class DelegateCommand : ICommand
{
private readonly Action action;
public DelegateCommand(Action action)
{
if (action == null)
throw new ArgumentNullException("action");
this.action = action;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute()
{
this.action();
}
bool ICommand.CanExecute(object parameter)
{
return true;
}
void ICommand.Execute(object parameter)
{
this.Execute();
}
}
class Person : ObservableObject
{
private string name, surname;
public Person()
{
}
public Person(string name, string surname)
{
this.name = name;
this.surname = surname;
}
public string Name
{
get { return this.name; }
set { this.Set(ref this.name, value, "Name"); }
}
public string Surname
{
get { return this.surname; }
set { this.Set(ref this.surname, value, "Surname"); }
}
public override string ToString()
{
return this.name + ' ' + this.surname;
}
}
class MainWindowModel : ObservableObject
{
public ObservableCollection<Person> People { get; private set; }
public SelectedItemCollection<Person> SelectedPeople { get; private set; }
public DelegateCommand SelectAllCommand { get; private set; }
public DelegateCommand UnselectAllCommand { get; private set; }
public DelegateCommand SelectNextRangeCommand { get; private set; }
public MainWindowModel()
{
this.People = new ObservableCollection<Person>(Enumerable.Range(1, 1000).Select(i => new Person("Name " + i, "Surname " + i)));
this.SelectedPeople = new SelectedItemCollection<Person>();
for (int i = 0; i < this.People.Count; i += 2)
this.SelectedPeople.Add(this.People[i]);
this.SelectAllCommand = new DelegateCommand(() => this.SelectedPeople.Reset(this.People));
this.UnselectAllCommand = new DelegateCommand(() => this.SelectedPeople.Clear());
this.SelectNextRangeCommand = new DelegateCommand(() =>
{
var index = this.SelectedPeople.Count > 0 ? this.People.IndexOf(this.SelectedPeople[this.SelectedPeople.Count - 1]) + 1 : 0;
int count = 10;
this.SelectedPeople.Reset(Enumerable.Range(index, count).Where(i => i < this.People.Count).Select(i => this.People[i]));
});
this.SelectedPeople.CollectionChanged += this.OnSelectedPeopleCollectionChanged;
}
private void OnSelectedPeopleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("Action = {0}, NewItems.Count = {1}, NewStartingIndex = {2}, OldItems.Count = {3}, OldStartingIndex = {4}, Total.Count = {5}", e.Action, e.NewItems != null ? e.NewItems.Count : 0, e.NewStartingIndex, e.OldItems != null ? e.OldItems.Count : 0, e.OldStartingIndex, this.SelectedPeople.Count);
}
}
class SelectedItemCollection<T> : ObservableCollection<T>
{
public void Reset(IEnumerable<T> items)
{
int oldCount = this.Count;
this.Items.Clear();
foreach (var item in items)
this.Items.Add(item);
if (!(oldCount == 0 && this.Count == 0))
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
if (this.Count != oldCount)
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
}
}
}
}
Following is the implementation, which unfortunately is not documented. Implementation uses various tricks (via reflection) to reduce the number of changes to underlying collections as much as possible (to suspend excessive collection changed notifications).
It is worth noting that if selected items collection in model has Select(IEnumerable) or Select(IEnumerable) method, that method will be used for performing bulk updates (updates which affect more than one item) which offers better performance if, for example, all items in the DataGrid are selected or un-selected.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Markup;
namespace WpfApplication
{
static class MultiSelectorExtension
{
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(MultiSelectorExtension), new PropertyMetadata(new PropertyChangedCallback(OnSelectedItemsChanged)));
private static readonly DependencyProperty SelectedItemsBinderProperty = DependencyProperty.RegisterAttached("SelectedItemsBinder", typeof(SelectedItemsBinder), typeof(MultiSelectorExtension));
[AttachedPropertyBrowsableForType(typeof(MultiSelector))]
[DependsOn("ItemsSource")]
public static IList GetSelectedItems(this MultiSelector multiSelector)
{
if (multiSelector == null)
throw new ArgumentNullException("multiSelector");
return (IList)multiSelector.GetValue(SelectedItemsProperty);
}
public static void SetSelectedItems(this MultiSelector multiSelector, IList selectedItems)
{
if (multiSelector == null)
throw new ArgumentNullException("multiSelector");
multiSelector.SetValue(SelectedItemsProperty, selectedItems);
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var multiSelector = d as MultiSelector;
if (multiSelector == null)
return;
var binder = (SelectedItemsBinder)multiSelector.GetValue(SelectedItemsBinderProperty);
var selectedItems = e.NewValue as IList;
if (selectedItems != null)
{
if (binder == null)
binder = new SelectedItemsBinder(multiSelector);
binder.SelectedItems = selectedItems;
}
else if (binder != null)
binder.Dispose();
}
private sealed class SelectedItemsBinder : IDisposable
{
private static readonly IList emptyList = new object[0];
private static readonly Action<MultiSelector> multiSelectorBeginUpdateSelectedItems, multiSelectorEndUpdateSelectedItems;
private readonly MultiSelector multiSelector;
private IList selectedItems;
private IResetter selectedItemsResetter;
private bool suspendMultiSelectorUpdate, suspendSelectedItemsUpdate;
static SelectedItemsBinder()
{
GetMultiSelectorBeginEndUpdateSelectedItems(out multiSelectorBeginUpdateSelectedItems, out multiSelectorEndUpdateSelectedItems);
}
public SelectedItemsBinder(MultiSelector multiSelector)
{
this.multiSelector = multiSelector;
this.multiSelector.SelectionChanged += this.OnMultiSelectorSelectionChanged;
this.multiSelector.Unloaded += this.OnMultiSelectorUnloaded;
this.multiSelector.SetValue(SelectedItemsBinderProperty, this);
}
public IList SelectedItems
{
get { return this.selectedItems; }
set
{
this.SetSelectedItemsChangedHandler(false);
this.selectedItems = value;
this.selectedItemsResetter = GetResetter(this.selectedItems.GetType());
this.SetSelectedItemsChangedHandler(true);
if (this.multiSelector.IsLoaded)
this.OnSelectedItemsCollectionChanged(this.selectedItems, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
else
{
RoutedEventHandler multiSelectorLoadedHandler = null;
this.multiSelector.Loaded += multiSelectorLoadedHandler = new RoutedEventHandler((sender, e) =>
{
this.OnSelectedItemsCollectionChanged(this.selectedItems, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
this.multiSelector.Loaded -= multiSelectorLoadedHandler;
});
}
}
}
private int ItemsSourceCount
{
get
{
var collection = this.multiSelector.ItemsSource as ICollection;
return collection != null ? collection.Count : -1;
}
}
public void Dispose()
{
this.multiSelector.ClearValue(SelectedItemsBinderProperty);
this.multiSelector.Unloaded -= this.OnMultiSelectorUnloaded;
this.multiSelector.SelectionChanged -= this.OnMultiSelectorSelectionChanged;
this.SetSelectedItemsChangedHandler(false);
}
private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.suspendMultiSelectorUpdate || e.Action == NotifyCollectionChangedAction.Move)
return;
this.suspendSelectedItemsUpdate = true;
if (this.selectedItems.Count == 0)
this.multiSelector.UnselectAll();
else if (this.selectedItems.Count == this.ItemsSourceCount)
this.multiSelector.SelectAll();
else if (e.Action != NotifyCollectionChangedAction.Reset && (e.NewItems == null || e.NewItems.Count <= 1) && (e.OldItems == null || e.OldItems.Count <= 1))
UpdateList(this.multiSelector.SelectedItems, e.NewItems ?? emptyList, e.OldItems ?? emptyList);
else
{
if (multiSelectorBeginUpdateSelectedItems != null)
{
multiSelectorBeginUpdateSelectedItems(this.multiSelector);
this.multiSelector.SelectedItems.Clear();
UpdateList(this.multiSelector.SelectedItems, this.selectedItems, emptyList);
multiSelectorEndUpdateSelectedItems(this.multiSelector);
}
else
{
this.multiSelector.UnselectAll();
UpdateList(this.multiSelector.SelectedItems, this.selectedItems, emptyList);
}
}
this.suspendSelectedItemsUpdate = false;
}
private void OnMultiSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (this.suspendSelectedItemsUpdate)
return;
this.suspendMultiSelectorUpdate = true;
if (e.AddedItems.Count <= 1 && e.RemovedItems.Count <= 1)
UpdateList(this.selectedItems, e.AddedItems, e.RemovedItems);
else
{
if (this.selectedItemsResetter != null)
this.selectedItemsResetter.Reset(this.selectedItems, this.multiSelector.SelectedItems.Cast<object>().Where(item => item != CollectionView.NewItemPlaceholder));
else
UpdateList(this.selectedItems, e.AddedItems, e.RemovedItems);
}
this.suspendMultiSelectorUpdate = false;
}
private void OnMultiSelectorUnloaded(object sender, RoutedEventArgs e)
{
this.Dispose();
}
private void SetSelectedItemsChangedHandler(bool add)
{
var notifyCollectionChanged = this.selectedItems as INotifyCollectionChanged;
if (notifyCollectionChanged != null)
{
if (add)
notifyCollectionChanged.CollectionChanged += this.OnSelectedItemsCollectionChanged;
else
notifyCollectionChanged.CollectionChanged -= this.OnSelectedItemsCollectionChanged;
}
}
private static void UpdateList(IList list, IList newItems, IList oldItems)
{
int addedCount = 0;
for (int i = 0; i < oldItems.Count; ++i)
{
var index = list.IndexOf(oldItems[i]);
if (index >= 0)
{
object newItem;
if (i < newItems.Count && (newItem = newItems[i]) != CollectionView.NewItemPlaceholder)
{
list[index] = newItem;
++addedCount;
}
else
list.RemoveAt(index);
}
}
for (int i = addedCount; i < newItems.Count; ++i)
{
var newItem = newItems[i];
if (newItem != CollectionView.NewItemPlaceholder)
list.Add(newItem);
}
}
private static void GetMultiSelectorBeginEndUpdateSelectedItems(out Action<MultiSelector> beginUpdateSelectedItems, out Action<MultiSelector> endUpdateSelectedItems)
{
try
{
beginUpdateSelectedItems = (Action<MultiSelector>)Delegate.CreateDelegate(typeof(Action<MultiSelector>), typeof(MultiSelector).GetMethod("BeginUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance));
endUpdateSelectedItems = (Action<MultiSelector>)Delegate.CreateDelegate(typeof(Action<MultiSelector>), typeof(MultiSelector).GetMethod("EndUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance));
}
catch
{
beginUpdateSelectedItems = endUpdateSelectedItems = null;
}
}
private static IResetter GetResetter(Type listType)
{
try
{
MethodInfo genericReset = null, nonGenericReset = null;
Type genericResetItemType = null;
foreach (var method in listType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
if (method.Name != "Reset")
continue;
if (method.ReturnType != typeof(void))
continue;
var parameters = method.GetParameters();
if (parameters.Length != 1)
continue;
var parameterType = parameters[0].ParameterType;
if (parameterType.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
genericResetItemType = parameterType.GetGenericArguments()[0];
genericReset = method;
break;
}
else if (parameterType == typeof(IEnumerable))
nonGenericReset = method;
}
if (genericReset != null)
return (IResetter)Activator.CreateInstance(typeof(GenericResetter<,>).MakeGenericType(genericReset.DeclaringType, genericResetItemType), genericReset);
else if (nonGenericReset != null)
return (IResetter)Activator.CreateInstance(typeof(NonGenericResetter<>).MakeGenericType(nonGenericReset.DeclaringType), nonGenericReset);
else
return null;
}
catch
{
return null;
}
}
private interface IResetter
{
void Reset(IList list, IEnumerable items);
}
private sealed class NonGenericResetter<TTarget> : IResetter
{
private readonly Action<TTarget, IEnumerable> reset;
public NonGenericResetter(MethodInfo method)
{
this.reset = (Action<TTarget, IEnumerable>)Delegate.CreateDelegate(typeof(Action<TTarget, IEnumerable>), method);
}
public void Reset(IList list, IEnumerable items)
{
this.reset((TTarget)list, items);
}
}
private sealed class GenericResetter<TTarget, T> : IResetter
{
private readonly Action<TTarget, IEnumerable<T>> reset;
public GenericResetter(MethodInfo method)
{
this.reset = (Action<TTarget, IEnumerable<T>>)Delegate.CreateDelegate(typeof(Action<TTarget, IEnumerable<T>>), method);
}
public void Reset(IList list, IEnumerable items)
{
this.reset((TTarget)list, items.Cast<T>());
}
}
}
}
}
Create a command that fires on the DataGrid's SelectionChanged event, passing in the DataGrid's SelectedItems.
In your ViewModel, have a List of selected objects.
Your SelectionChangedCommand execution method would then update that collection of selected objects.
For example:
In my XAML:
<DataGrid ItemsSource="{Binding Datasets, NotifyOnTargetUpdated=True}" Name="dsDatagrid" SelectionMode="Extended" MouseDoubleClick="ViewDataset">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding ElementName=dsDatagrid, Path=SelectedItems}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
In my ViewModel:
private List<ObservableDataset> selectedDatasets;
private void SelectionChangedExecuted(object datasets)
{
this.selectedDatasets = new List<ObservableDataset>((datasets as IList).Cast<ObservableDataset>());
}
EDIT: I'm using MVVMLight.
Hi
There is a behavior in following code
I want to use this behavior in my WPF application
But i cant attached this behavior to my project
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.ComponentModel;
namespace Brainsiders.MouseOver3D
{
[Description("MouseOver3D - A behavior that applies the MouseOver Design Interaction Pattern to a 3D environment.")]
public class MouseOver3D : TargetedTriggerAction<FrameworkElement>
{
private TimeSpan hoverUp_duration = TimeSpan.FromSeconds(0.5);
[Category("Mouse Over 3D - Going Up")]
public TimeSpan HoverUp_duration
{
get { return hoverUp_duration; }
set { hoverUp_duration = value; }
}
private TimeSpan hoverDown_Duration = TimeSpan.FromSeconds(0.9);
[Category("Mouse Over 3D - Going Down")]
public TimeSpan HoverDown_Duration
{
get { return hoverDown_Duration; }
set { hoverDown_Duration = value; }
}
[Category("Mouse Over 3D - Going Up")]
public IEasingFunction HoverUp_Easing { get; set; }
[Category("Mouse Over 3D - Going Down")]
public IEasingFunction HoverDown_Easing { get; set; }
private double hoverOffset = 30;
[Category("Mouse Over 3D - General")]
public double HoverOffset
{
get { return hoverOffset; }
set { hoverOffset = value; }
}
private FrameworkElement feAssociatedObject;
private FrameworkElement feSourceObject;
private FrameworkElement feTargetObject;
private PlaneProjection ProjectionTargetObject;
private Storyboard SB_HoverZ;
protected override void Invoke(object parameter)
{
FrameworkElement myElement = this.AssociatedObject as FrameworkElement;
}
protected override void OnAttached()
{
base.OnAttached();
feAssociatedObject = (FrameworkElement)this.AssociatedObject;
feSourceObject = (FrameworkElement)this.AssociatedObject;
feSourceObject.Loaded += new RoutedEventHandler(feSourceObject_Loaded);
}
void feSourceObject_Loaded(object sender, RoutedEventArgs e)
{
feSourceObject.Loaded -= new RoutedEventHandler(feSourceObject_Loaded);
ProjectionTargetObject = new PlaneProjection();
feTargetObject = (FrameworkElement)this.TargetObject;
if (feTargetObject == null) feTargetObject = feAssociatedObject;
if (feTargetObject.Projection == null)
{
feTargetObject.RenderTransformOrigin = new Point(0.5, 0.5);
PlaneProjection pj = new PlaneProjection();
feTargetObject.Projection = pj;
}
feSourceObject.MouseEnter += new MouseEventHandler(feSourceObject_MouseEnter);
feSourceObject.MouseLeave += new MouseEventHandler(feSourceObject_MouseLeave);
}
protected override void OnDetaching()
{
base.OnDetaching();
}
void feSourceObject_MouseLeave(object sender, MouseEventArgs e)
{
DeactivateAnimation();
}
void feSourceObject_MouseEnter(object sender, MouseEventArgs e)
{
ActivateAnimation();
}
bool bAnimationActivated = false;
private void ActivateAnimation()
{
if (bAnimationActivated == false)
{
AnimateHoverZ(HoverOffset, true);
bAnimationActivated = true;
}
}
private void DeactivateAnimation()
{
if (bAnimationActivated == true)
{
AnimateHoverZ(0, false);
bAnimationActivated = false;
}
}
private void AnimateHoverZ( Double Z, bool HoverUp)
{
if (HoverUp == true)
playAnimation(feTargetObject, "(UIElement.Projection).(PlaneProjection.LocalOffsetZ)", HoverUp_duration, Z, SB_HoverZ, HoverUp_Easing);
else
playAnimation(feTargetObject, "(UIElement.Projection).(PlaneProjection.LocalOffsetZ)", HoverDown_Duration, Z, SB_HoverZ, HoverDown_Easing);
}
public static void playAnimation(FrameworkElement element, string property, TimeSpan time, double value, Storyboard sb, IEasingFunction EasingFunction)
{
sb = new Storyboard();
sb.Children.Clear();
DoubleAnimation animation = new DoubleAnimation();
animation.Duration = time;
animation.To = value;
animation.EasingFunction = EasingFunction;
Storyboard.SetTargetProperty(animation, new PropertyPath(property));
Storyboard.SetTarget(animation, element);
sb.Children.Add(animation);
sb.Begin();
}
}
}
You need to add the necessary references (System.Windows.Interactivity)
to your project, for that you need to have some version of the Blend SDK installed.
Then to use this in XAML you need to define namespaces for both interactivity and this behavior:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:bs="clr-namespace:Brainsiders.MouseOver3D"
Then you should be able to attach it like this:
<Button>
<i:Interaction.Behaviors>
<bs:MouseOver3D />
</i:Interaction.Behaviors>
</Button>