i need an ItemsControl that displays only the selected Item so i have written a customized ItemsControl as seen here:
public class TabView : ItemsControl
{
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
"SelectedItem",
typeof(object),
typeof(TabView),
new PropertyMetadata(new PropertyChangedCallback(SelectedItemPropertyChanged)));
private static void SelectedItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//throw new NotImplementedException();
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { OnSelectedItemChanged(value); }
}
public TabView()
: base()
{
}
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (!IsItem(SelectedItem))
{
if (Items.Count > 0)
SelectedItem = Items[0];
else
SelectedItem = null;
}
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return false;
}
protected override DependencyObject GetContainerForItemOverride()
{
Grid grid = new Grid();
grid.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
grid.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
return grid;
}
protected virtual void OnSelectedItemChanged(object newItem)
{
if (SelectedItem == newItem) return;
if (!IsItem(newItem))
{
Debugger.Log(0, "TRACE", "TABVIEW: Index Out of Bounds"+Environment.NewLine);
return;
}
foreach (var item in Items)
{
var container = ItemContainerGenerator.ContainerFromItem(item);
if (container != null)
{
if (item == newItem)
container.SetValue(Control.VisibilityProperty, Visibility.Visible);
else
container.SetValue(Control.VisibilityProperty, Visibility.Collapsed);
}
}
SetValue(SelectedItemProperty, newItem);
}
private bool IsItem(object item)
{
return Items.Contains(item);
}
}
Now I bind my Dataobject SelectedDataTab (MainViewModel.SelectedDataTab) to TabView.SelectedItem.
<uc:TabView
ItemsSource="{Binding BrowserTabs}"
SelectedItem="{Binding SelectedDataTab}">
<uc:TabView.ItemTemplate>
<DataTemplate>
<local:SlitteTabViewSelector Content="{Binding}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<local:SlitteTabViewSelector.Browser>
<DataTemplate>
<uc:CustomBrowser Margin="0,0,0,0" />
</DataTemplate>
</local:SlitteTabViewSelector.Browser>
<local:SlitteTabViewSelector.MoreTabs>
<DataTemplate>
<uc:MoreTabsControl Margin="0,0,0,0" />
</DataTemplate>
</local:SlitteTabViewSelector.MoreTabs>
</local:SlitteTabViewSelector>
</DataTemplate>
</uc:TabView.ItemTemplate>
</uc:TabView>
If I now start changing SelectedDataTab in my MainViewModel TabView.SelectedItem won't change its value! Did I miss something? I thought if I bind that Property to my MainViewModel.SelectedDataTab it's value gets updated depending on the bound value.
Update: My MainViewModel implements INotifyPropertyChanged correctly and SelectedDataTab will fire it if changed.
RESOLVED
First of all not the Property getter/setter is fired by Binding instead SelectedItemPropertyChanged gets fired.
But in my code SelectedItemPropertyChanged will not get fired because
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (!IsItem(SelectedItem))
{
if (Items.Count > 0)
SelectedItem = Items[0];
else
SelectedItem = null;
}
}
overrides the DataBinding. ;)
Related
I'm using MVVM and have the following problem. My TextBox.Text is bound with UpdateSourceTrigger=LostFocus (thats what the user want). I have a Button with a SaveCommand CommandBinding - this works. Now i have a KeyBinding with Strg+S wich also execute the SaveCommand. And here is the problem: when i m in the Textbox and press Strg+s, the changes are not in the viewmodel.
is there any way to get MVVM Commands with KeyBinding and TextBox UpdateSourceTrigger=LostFocus working together?
some code to check out the problem
<Window>
<Window.InputBindings>
<KeyBinding Key="S" Modifiers="Control" Command="{Binding SaveCommand}"></KeyBinding>
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding MyText1, UpdateSourceTrigger=LostFocus}" Width="100"></TextBox>
<Button Grid.Row="1" Content="_Save" Command="{Binding SaveCommand}" IsDefault="True"></Button>
</Grid>
</Window>
public partial class MainWindow : Window
{
private Viewmodel _data;
public MainWindow()
{
_data = new Viewmodel();
InitializeComponent();
this.DataContext = _data;
}
}
public class Viewmodel : INPCBase
{
private string _myText1;
private Lazy<DelegateCommand> _save;
public Viewmodel()
{
this._save = new Lazy<DelegateCommand>(()=> new DelegateCommand(this.SaveCommandExecute));
}
private void SaveCommandExecute()
{
MessageBox.Show(MyText1);
}
public string MyText1
{
get { return _myText1; }
set { _myText1 = value; this.NotifyPropertyChanged(()=>MyText1);}
}
public ICommand SaveCommand
{
get { return _save.Value; }
}
}
at the moment i came up with the following workaround. within the usercontrol/views where i define my KeyBindings, i also listen to the PreviewKeyDown event and set the focus to the next element when eg. Strg+S is pressed.
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.S && e.KeyboardDevice.Modifiers == ModifierKeys.Control)
{
var fe = Keyboard.FocusedElement as UIElement;
if (fe != null)
{
fe.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
I have the same problem and end up with attached property for TextBox.
public static bool GetCommitOnSave(DependencyObject obj)
{
return (bool)obj.GetValue(CommitOnSaveProperty);
}
public static void SetCommitOnSave(DependencyObject obj, bool value)
{
obj.SetValue(CommitOnSaveProperty, value);
}
public static readonly DependencyProperty CommitOnSaveProperty =
DependencyProperty.RegisterAttached("CommitOnSave", typeof(bool), typeof(Helper), new PropertyMetadata(false, CommitOnSavePropertyChanged));
private static void CommitOnSavePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox textBox)
{
if ((bool)e.NewValue)
{
if ((bool)e.NewValue)
{
textBox.KeyDown += TextBox_KeyDown;
}
else
{
textBox.KeyDown -= TextBox_KeyDown;
}
}
}
}
private static void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var textBox = (TextBox)sender;
if (e.Key == Key.S && Keyboard.Modifiers == ModifierKeys.Control)
{
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
}
}
Using <TextBox Text="{Binding Name}" local:Helper.CommitOnSave="True" />
Of course you can set attached property in style for all TextBoxes in a form.
I think I find the best solution for me. I mix solution #blindmeis and my previous one with using attached property.
I create command which update binding source of actual keyboard focused element:
public class CommitValueCommand : ICommand
{
private static CommitValueCommand _instance;
public static CommitValueCommand Command => _instance ?? (_instance = new CommitValueCommand());
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (Keyboard.FocusedElement is TextBox textBox)
{
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
}
//for combobox etc.
else if (Keyboard.FocusedElement is Selector selector)
{
BindingOperations.GetBindingExpression(selector, Selector.SelectedValueProperty).UpdateSource();
}
}
}
In Execute method of command SaveCommand just at beginning invoke CommitValueCommand.Command.Execute().
I have a ListBox and I simply want to bind the J and K keys to whatever commands the up and down arrow keys are bound to. The up and down arrow keys in a WPF listbox typically change the selected item to the previous/next item. I thought something like this should work:
<ListBox.InputBindings>
<KeyBinding Key="J" Command="ScrollBar.LineDownCommand" />
<KeyBinding Key="K" Command="ScrollBar.LineUpCommand" />
</ListBox.InputBindings>
I'm probably being too simplistic here.
You can use your DependencyClass on the commands. Define the commands in ListBox.InputBindings:
XAML
<ListBox Name="SampleListBox" Width="200" Height="200" KeyboardNavigation.DirectionalNavigation="Cycle" SelectedIndex="{Binding MySelectedIndex}">
<ListBox.InputBindings>
<KeyBinding Command="{Binding NextCommand}" Gesture="CTRL+J" />
<KeyBinding Command="{Binding PrevCommand}" Gesture="CTRL+K" />
</ListBox.InputBindings>
<ListBoxItem>Sample 1</ListBoxItem>
<ListBoxItem>Sample 2</ListBoxItem>
<ListBoxItem>Sample 3</ListBoxItem>
<ListBoxItem>Sample 4</ListBoxItem>
</ListBox>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Set your data
this.DataContext = new MainWindowViewModel();
// Set focus
SampleListBox.Focus();
}
}
/// <summary>
/// Class with commands
/// </summary>
public class MainWindowViewModel : DependencyObject
{
public ICommand NextCommand
{
get;
set;
}
public ICommand PrevCommand
{
get;
set;
}
public int MySelectedIndex
{
get
{
return (int)GetValue(MySelectedIndexProperty);
}
set
{
SetValue(MySelectedIndexProperty, value);
}
}
public static readonly DependencyProperty MySelectedIndexProperty =
DependencyProperty.Register("MySelectedIndex", typeof(int), typeof(MainWindowViewModel), new UIPropertyMetadata(0));
public MainWindowViewModel()
{
MySelectedIndex = 0;
NextCommand = new SimpleCommand(SetNext);
PrevCommand = new SimpleCommand(SetPrev);
}
private void SetNext()
{
MySelectedIndex += 1;
}
private void SetPrev()
{
if (MySelectedIndex > 0)
{
MySelectedIndex -= 1;
}
}
}
public class SimpleCommand : ICommand
{
private Action _action;
public SimpleCommand(Action p_action)
{
_action = p_action;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (_action != null)
{
_action();
}
}
}
In the class contains two ICommand's: NextCommand and PrevCommand. Also there is a DependencyProperty MySelectedIndex, which contains the current index of the item. In SimpleCommand always return true.
This is just an example that still need to check the total number of Items ListBox. Or instead of increasing the SelectedIndex, use ScrollViewer logic.
Extension
Example with ScrollViewer:
To scroll through the items in the ListBox, you must first have access to it. Below is the corresponding function:
public static DependencyObject GetScrollViewer(DependencyObject Object)
{
if (Object is ScrollViewer)
{
return Object;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(Object); i++)
{
var child = VisualTreeHelper.GetChild(Object, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
Simple function scrolling:
private void OnScrollDown(object sender, RoutedEventArgs e)
{
if (MyListBox.Items.Count > 0)
{
// Get ScrollViewer from ListBox
ScrollViewer scrollViewer = GetScrollViewer(MyListBox) as ScrollViewer;
if (scrollViewer != null)
{
// Increment offset - scrolling Down, sub - scrolling Up
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + ScrollListBoxOffset);
}
}
}
Here is my ViewModel class:
public class ColumnViewModel : ViewModelBase
{
private string bindingPropName;
public string BindingPropName
{
get { return bindingPropName; }
set
{
if (bindingPropName != value)
{
bindingPropName = value;
RaisePropertyChanged("BindingPropName");
}
}
}
private string header;
public string Header
{
get { return header; }
set
{
if (header != value)
{
header = value;
RaisePropertyChanged("Header");
}
}
}
}
DataGrid extension classes:
public static class DataGridColumns
{
static DataGridColumns()
{
FrameworkElement.DataContextProperty.AddOwner(typeof(DataGridTextColumn));
}
private static readonly DependencyProperty DataGridColumnSettingsProperty = DependencyProperty.RegisterAttached(
"DataGridColumnSettings",
typeof(DataGridColumnSettings),
typeof(DataGridColumn));
private static void SetDataGridColumnSettings(DataGridColumn column, DataGridColumnSettings settings) { column.SetValue(DataGridColumnSettingsProperty, settings); }
private static DataGridColumnSettings GetDataGridColumnSettings(DataGridColumn column) { return column.GetValue(DataGridColumnSettingsProperty) as DataGridColumnSettings; }
public static readonly DependencyProperty DisplayColumnsProperty = DependencyProperty.RegisterAttached(
"DisplayColumns",
typeof(IList),
typeof(DataGridColumns),
new PropertyMetadata(null, DisplayColumnsPropertyChanged));
public static void SetDisplayColumns(DataGrid dataGrid, IList columns) { dataGrid.SetValue(DisplayColumnsProperty, columns); }
public static IList GetDisplayColumns(DataGrid dataGrid) { return dataGrid.GetValue(DisplayColumnsProperty) as IList; }
private static void DisplayColumnsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as DataGrid;
var columns = e.NewValue as IList;
var template = GetColumnSettingsTemplate(target);
CreateColumns(target, columns, template);
}
public static readonly DependencyProperty ColumnSettingsTemplateProperty = DependencyProperty.RegisterAttached(
"ColumnSetupTemplate",
typeof(DataTemplate),
typeof(DataGridColumns),
new PropertyMetadata(null, ColumnSettingsTemplateChanged));
public static void SetColumnSettingsTemplate(DataGrid dataGrid, DataTemplate columnSetupTemplate) { dataGrid.SetValue(ColumnSettingsTemplateProperty, columnSetupTemplate); }
public static DataTemplate GetColumnSettingsTemplate(DataGrid dataGrid) { return dataGrid.GetValue(ColumnSettingsTemplateProperty) as DataTemplate; }
private static void ColumnSettingsTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as DataGrid;
var columns = GetDisplayColumns(target);
var template = e.NewValue as DataTemplate;
CreateColumns(target, columns, template);
}
private static void CreateColumns(DataGrid dataGrid, IList columnViewModels, DataTemplate columnSettings)
{
if (dataGrid == null)
return;
dataGrid.Columns.Clear();
if (columnViewModels == null)
return;
foreach (var column in columnViewModels)
{
var newColumn = new DataGridTextColumn();
newColumn.SetValue(FrameworkElement.DataContextProperty, column);
if (columnSettings != null)
{
var settings = columnSettings.LoadContent() as DataGridColumnSettings;
if (settings != null)
{
settings.Setup(newColumn, column);
SetDataGridColumnSettings(newColumn, settings);
}
}
dataGrid.Columns.Add(newColumn);
}
}
}
public class DataGridColumnSettings : FrameworkElement
{
public static readonly DependencyProperty ColumnBindingPathProperty = DependencyProperty.Register(
"ColumnBindingPath",
typeof(string),
typeof(DataGridColumnSettings),
new PropertyMetadata(null, ColumnBindingPathChanged));
public string ColumnBindingPath
{
get { return GetValue(ColumnBindingPathProperty) as string; }
set { SetValue(ColumnBindingPathProperty, value); }
}
private static void ColumnBindingPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as DataGridColumnSettings;
if (target == null)
return;
target.column.Binding = new Binding(e.NewValue as string);
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
"Header",
typeof(object),
typeof(DataGridColumnSettings));
public object Header
{
get { return GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
private DataGridTextColumn column;
private object viewModel;
public void Setup(DataGridTextColumn column, object columnViewModel)
{
this.column = column;
viewModel = columnViewModel;
this.DataContext = columnViewModel;
if (Header is FrameworkElement)
{
(Header as FrameworkElement).DataContext = columnViewModel;
column.Header = Header;
}
else
BindingOperations.SetBinding(column, DataGridColumn.HeaderProperty, new Binding("Header") { Source = this });
column.Binding = new Binding(ColumnBindingPath);
}
}
and my XAML code:
<DataGrid t:DataGridColumns.DisplayColumns="{Binding Columns}" ItemsSource="{Binding Rows}" AutoGenerateColumns="False">
<t:DataGridColumns.ColumnSettingsTemplate>
<DataTemplate>
<t:DataGridColumnSettings ColumnBindingPath="{Binding BindingPropName}">
<t:DataGridColumnSettings.Header>
<TextBlock Text="{Binding Header}"/>
</t:DataGridColumnSettings.Header>
</t:DataGridColumnSettings>
</DataTemplate>
</t:DataGridColumns.ColumnSettingsTemplate>
</DataGrid>
Everything what i'm trying to achieve is adding CellTemplate:
<DataGrid t:DataGridColumns.DisplayColumns="{Binding Columns}" ItemsSource="{Binding Rows}" AutoGenerateColumns="False">
<t:DataGridColumns.ColumnSettingsTemplate>
<DataTemplate>
<t:DataGridColumnSettings ColumnBindingPath="{Binding BindingPropName}">
<t:DataGridColumnSettings.Header>
<TextBlock Text="{Binding Header}"/>
</t:DataGridColumnSettings.Header>
<t:DataGridColumnSettings.CellTemplate>
<TextBlock Text="{Binding ColumnBindingPath}"/>
</t:DataGridColumnSettings.CellTemplate>
</t:DataGridColumnSettings>
</DataTemplate>
</t:DataGridColumns.ColumnSettingsTemplate>
</DataGrid>
I think the easiest approach would be to add one more dependency property to take a cell template. Then if this exist, create DataGridTemplateColumn instead of DataGridTextColumn when filling the DataGrid, but i have a little problem with Binding my CellTemplate TextBlock to ColumnBindingPath dependency property. Please help ...
The syntax for binding to an attached property would be:
<TextBlock Text="{Binding Path=(t.DataGridColumns.ColumnBindingPath)}"/>
The Path= needs to be written explicitly when binding to attached properties. This has been fixed in WPF 4.5, so there it may be sufficient to write
<TextBlock Text="{Binding (t.DataGridColumns.ColumnBindingPath)}"/>
Note, that you still need parenthesis around the property. Does this answer your question already?
I have an wpf mvvm application. I try to write checkbox list control.
I can bind the checkbox list elements.
Added to this issue, I want to get sum of the selected checkbox list elements values.
I added DependencyProperty and bind it to view model property.
But, they dont fire each other.
CheckBoxList User Control Xaml
<ListBox x:Name="ItemsControl" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsSelected, Mode=TwoWay}"
Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
CheckBoxList Code Behind
public partial class CheckBoxList : UserControl
{
public CheckBoxList()
{
InitializeComponent();
}
public static readonly DependencyProperty SelectedCheckBoxItemsValueProperty =
DependencyProperty.Register("SelectedCheckBoxItemsValue", typeof(int), typeof(CheckBoxList),
new FrameworkPropertyMetadata(
0,
new FrameworkPropertyMetadata(0, OnSelectedItemsChanged));
public int SelectedCheckBoxItemsValue
{
get { return (int)GetValue(SelectedCheckBoxItemsValueProperty); }
set { SetValue(SelectedCheckBoxItemsValueProperty, value); }
}
private static int GetSelectedCheckBoxItemsValue(DependencyObject obj)
{
return (int)obj.GetValue(SelectedCheckBoxItemsValueProperty);
}
private static void OnSelectedItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
CheckBoxList checkboxList = obj as CheckBoxList;
ObservableCollection<ISelectableItem> items = checkboxList.DataContext as ObservableCollection<ISelectableItem>;
foreach (var item in items)
{
item.IsSelected = (GetSelectedCheckBoxItemsValue(obj) & item.Value) != 0;
}
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBoxList checkboxList = sender as CheckBoxList;
ObservableCollection<ISelectableItem> coll = ItemsControl.DataContext as ObservableCollection<ISelectableItem>;
if (coll == null) return;
int count = 0;
foreach (var item in coll)
{
if (item.IsSelected)
{
count += item.Value;
}
}
SelectedCheckBoxItemsValue = count;
}
}
SelectableItem Class
public interface ISelectableItem : INotifyPropertyChanged
{
bool IsSelected { get; set; }
string Text { get; set; }
int Value { get; set; }
string GroupName { get; set; }
}
public class SelectableItem : ISelectableItem
{ ....
ViewModel Property
public int SelectedCheckBoxEnumItemsValue
{
get
{
return _selectedCheckBoxEnumItemsValue;
}
set
{
_selectedCheckBoxEnumItemsValue = value;
NotifyOfPropertyChange("SelectedCheckBoxEnumItemsValue");
}
}
At Binder Class
string selectedItemPropertyName = "Selected" + viewModelProperty.Name + "Value";
var property = viewModelProperties.FirstOrDefault(p => p.Name.Contains(selectedItemPropertyName));
if (property != null)
{
var selectedItemOrValueBinding = new Binding(property.Name)
{
Mode = property.CanWrite ? BindingMode.TwoWay : BindingMode.OneWay,
ValidatesOnDataErrors = Attribute.GetCustomAttributes(property, typeof(ValidationAttribute), true).Any()
};
BindingOperations.SetBinding(control, CheckBoxList.SelectedCheckBoxItemsValueProperty, selectedItemOrValueBinding);
}
Below code solves your problem..
Please Note the segrgation of view models.
<StackPanel>
<TextBlock Text="{Binding Count}"></TextBlock>
<ListBox x:Name="ItemsControl" ItemsSource="{Binding CheckList}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Name="item" Content="{Binding Text}" IsChecked="{Binding IsSelected, Mode=TwoWay}" Command="{Binding CheckboxCheckedCommand}" CommandParameter="{Binding IsChecked, ElementName=item}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MasterViewModel();
}
}
public class MasterViewModel : INotifyPropertyChanged
{
private List<CheckBoxItem> checkList;
private int count;
public int Count
{
get
{
return count;
}
set
{
count = value;
OnPropertyChanged("Count");
}
}
public List<CheckBoxItem> CheckList
{
get
{
return checkList;
}
set
{
checkList = value;
OnPropertyChanged("CheckList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MasterViewModel()
{
checkList = new List<CheckBoxItem>();
for (int i = 0; i < 5; i++)
{
CheckBoxItem item = new CheckBoxItem();
item.Text = i.ToString();
item.IsSelected = false;
item.CheckboxCheckedCommand = new RelayCommand(new Action<object>(ExecuteCheckCommand));
checkList.Add(item);
}
}
private void ExecuteCheckCommand(object parameter)
{
if (parameter.GetType() == typeof(bool))
{
bool value = bool.Parse(parameter.ToString());
int val = count;
if (value)
{
val++;
}
else
{
val--;
}
Count = val;
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
public class CheckBoxItem : INotifyPropertyChanged
{
private bool isSelected;
private string text;
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
}
}
public bool IsSelected
{
get
{
return isSelected;
}
set
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
public ICommand CheckboxCheckedCommand
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
public class RelayCommand : ICommand
{
private Action<object> executeCommand;
public RelayCommand(Action<object> executeCommand)
{
this.executeCommand = executeCommand;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
executeCommand(parameter);
}
}
I use this:
<TextBox x:Name="Test"/>
<TextBlock Text="{Binding SelectionStart, ElementName=Test}"/>
but it always shows 0.
How can I treat it?
Thank you.
I ran into this problem (SelectionStart and SelectionLength are not dependency properties) and decided to make a TextBox with bindable selection start and end:
public class SelectionBindingTextBox : TextBox
{
public static readonly DependencyProperty BindableSelectionStartProperty =
DependencyProperty.Register(
"BindableSelectionStart",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionStartChanged));
public static readonly DependencyProperty BindableSelectionLengthProperty =
DependencyProperty.Register(
"BindableSelectionLength",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionLengthChanged));
private bool changeFromUI;
public SelectionBindingTextBox() : base()
{
this.SelectionChanged += this.OnSelectionChanged;
}
public int BindableSelectionStart
{
get
{
return (int)this.GetValue(BindableSelectionStartProperty);
}
set
{
this.SetValue(BindableSelectionStartProperty, value);
}
}
public int BindableSelectionLength
{
get
{
return (int)this.GetValue(BindableSelectionLengthProperty);
}
set
{
this.SetValue(BindableSelectionLengthProperty, value);
}
}
private static void OnBindableSelectionStartChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (!textBox.changeFromUI)
{
int newValue = (int)args.NewValue;
textBox.SelectionStart = newValue;
}
else
{
textBox.changeFromUI = false;
}
}
private static void OnBindableSelectionLengthChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (!textBox.changeFromUI)
{
int newValue = (int)args.NewValue;
textBox.SelectionLength = newValue;
}
else
{
textBox.changeFromUI = false;
}
}
private void OnSelectionChanged(object sender, RoutedEventArgs e)
{
if (this.BindableSelectionStart != this.SelectionStart)
{
this.changeFromUI = true;
this.BindableSelectionStart = this.SelectionStart;
}
if (this.BindableSelectionLength != this.SelectionLength)
{
this.changeFromUI = true;
this.BindableSelectionLength = this.SelectionLength;
}
}
}
You cannot bind to SelectionStart because it is not a DependencyProperty.
This could be an alternate solution:
View:
<TextBox Text="{Binding Text}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<mvvml:EventToCommand Command="{Binding TextBoxSelectionChangedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
ViewModel:
#region TextBoxSelectionChangedCommand
RelayCommand<RoutedEventArgs> _TextBoxSelectionChangedCommand = null;
public ICommand TextBoxSelectionChangedCommand {
get {
if (_TextBoxSelectionChangedCommand == null) {
_TextBoxSelectionChangedCommand = new RelayCommand<RoutedEventArgs>((r) => TextBoxSelectionChanged(r), (r) => true);
}
return _TextBoxSelectionChangedCommand;
}
}
protected virtual void TextBoxSelectionChanged(RoutedEventArgs _args) {
YourCursorPositionVariable = (_args.OriginalSource as System.Windows.Controls.TextBox).SelectionStart;
}
#endregion
I agree you has to cast TextBox component type in ViewModel and it's a kind of coupling, but create a custom component will impose to bind on a specific property as well.
As far as I am aware, this feature has not been included in Silverlight 2.0.
Read this article for a work-around solution.