WPF UI control move position via data binding - wpf

Please refer to the Model,ViewModel and View below.
The Textblock control on the view is not getting moved/updated to new position on the screen once i have set the value of the databinding property in the corresponding ViewModel.
Model:
private int _Text_1_Left;
public int Text_1_Left
{
get { return _Text_1_Left; }
set
{
if (_Text_1_Left != value)
{
_Text_1_Left = value;
}
}
}
ViewModel:
public class MyViewModel:INotifyPropertyChanged
{
private int _Text_1_Left = 50;
public int Text_1_Left
{
get { return _Text_1_Left; }
set
{
if (_Text_1_Left != value)
{
_Text_1_Left = value;
RaisePropertyChangedEvent("Text_1_Left");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChangedEvent(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
View(XAML):
<canvas>
<TextBlock Canvas.Left="{Binding ObjMyVM.Text_1_Left,Mode=OneWay}" Canvas.Top="10" />
</canvas>
View(Xaml.cs)
I am setting the DataBinding property in code behind of View.
public form_load()
{
MyViewModel objMyVM=new MyViewModel()
this.DataContext=objMyVM;
}
private void cmdMoveLeft_MouseUp(object sender, MouseButtonEventArgs e)
{
double Left;
TextBlock lbl_curr_selected;
lbl_curr_selected = Button_Text_1;
if (lbl_curr_selected != null)
{
Left = CurrentRec.Text_1_Left;
Left = Left <= 0 ? 0 : Left - 5;
ObjMyVM.Text_1_Left =int.Parse(Left.ToString()) ;
}
}
No error but textblock is supposed to move left by 5,which it is currently not doing.
Please show me how to move the position of a control element on the UI by changing the binding from code-behind.

As you have set the data context to the object your binding should just be:
Canvas.Left="{Binding Text_1_Left,Mode=OneWay}"
Check the console output for the error.

Related

Custom WPF DataGrid to select one or multiple rows manually from ViewModel

I try to create a DataGrid for WPF / MVVM which allows to manually select one ore more items from ViewModel code.
As usual the DataGrid should be able to bind its ItemsSource to a List / ObservableCollection. The new part is that it should maintain another bindable list, the SelectedItemsList. Each item added to this list should immediately be selected in the DataGrid.
I found this solution on Stackoverflow: There the DataGrid is extended to hold a Property / DependencyProperty for the SelectedItemsList:
public class CustomDataGrid : DataGrid
{
public CustomDataGrid()
{
this.SelectionChanged += CustomDataGrid_SelectionChanged;
}
private void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList = this.SelectedItems;
}
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList",
typeof(IList),
typeof(CustomDataGrid),
new PropertyMetadata(null));
}
In the View/XAML this property is bound to the ViewModel:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ucc:CustomDataGrid Grid.Row="0"
ItemsSource="{Binding DataGridItems}"
SelectionMode="Extended"
AlternatingRowBackground="Beige"
SelectionUnit="FullRow"
IsReadOnly="True"
SelectedItemsList="{Binding DataGridSelectedItems,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Row="1"
Margin="5"
HorizontalAlignment="Center"
Content="Select some rows"
Command="{Binding CmdSelectSomeRows}"/>
</Grid>
The ViewModel also implements the command CmdSelectSomeRows which selects some rows for testing. The ViewModel of the test application looks like this:
public class CustomDataGridViewModel : ObservableObject
{
public IList DataGridSelectedItems
{
get { return dataGridSelectedItems; }
set
{
dataGridSelectedItems = value;
OnPropertyChanged(nameof(DataGridSelectedItems));
}
}
public ICommand CmdSelectSomeRows { get; }
public ObservableCollection<ExamplePersonModel> DataGridItems { get; private set; }
public CustomDataGridViewModel()
{
// Create some example items
DataGridItems = new ObservableCollection<ExamplePersonModel>();
for (int i = 0; i < 10; i++)
{
DataGridItems.Add(new ExamplePersonModel
{
Name = $"Test {i}",
Age = i * 22
});
}
CmdSelectSomeRows = new RelayCommand(() =>
{
if (DataGridSelectedItems == null)
{
DataGridSelectedItems = new ObservableCollection<ExamplePersonModel>();
}
else
{
DataGridSelectedItems.Clear();
}
DataGridSelectedItems.Add(DataGridItems[0]);
DataGridSelectedItems.Add(DataGridItems[1]);
DataGridSelectedItems.Add(DataGridItems[4]);
DataGridSelectedItems.Add(DataGridItems[6]);
}, () => true);
}
private IList dataGridSelectedItems = new ArrayList();
}
This works, but only partially: After application start when items are added to the SelectedItemsList from ViewModel, they are not displayed as selected rows in the DataGrid. To get it to work I must first select some rows with the mouse. When I then add items to the SelectedItemsList from ViewModel these are displayed selected – as I want it.
How can I achieve this without having to first select some rows with the mouse?
You should subscribe to the Loaded event in your CustomDataGrid and initialize the SelectedItems of the Grid (since you never entered the SelectionChangedEvent, there is no link between the SelectedItemsList and the SelectedItems of your DataGrid.
private bool isSelectionInitialization = false;
private void CustomDataGrid_Loaded(object sender, RoutedEventArgs e)
{
this.isSelectionInitialization = true;
foreach (var item in this.SelectedItemsList)
{
this.SelectedItems.Clear();
this.SelectedItems.Add(item);
}
this.isSelectionInitialization = false;
}
and the SelectionChanged event handler has to be modified like this:
private void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!this.isSelectionInitialization)
{
this.SelectedItemsList = this.SelectedItems;
}
else
{
//Initialization from the ViewModel
}
}
Note that while this will fix your problem, this won't be a true synchronization as it will only copy the items from the ViewModel at the beginning.
If you need to change the items in the ViewModel at a later time and have it reflected in the selection let me know and I will edit my answer.
Edit: Solution to have a "true" synchronization
I created a class inheriting from DataGrid like you did.
You will need to add the using
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
public class CustomDataGrid : DataGrid
{
public CustomDataGrid()
{
this.SelectionChanged += CustomDataGrid_SelectionChanged;
this.Loaded += CustomDataGrid_Loaded;
}
private void CustomDataGrid_Loaded(object sender, RoutedEventArgs e)
{
//Can't do it in the constructor as the bound values won't be initialized
//If it is expected for the bound collection to be null initially, you could subscribe to the change of the
//dependency in order to subscribe to the collectionChanged event on the first non null value
this.SelectedItemsList.CollectionChanged += SelectedItemsList_CollectionChanged;
//We call the update in case we have already some items in the VM collection
this.UpdateUIWithSelectedItemsFromVm();
if(this.SelectedItems.Count != 0)
{
//Otherwise the items won't be as visible unless you change the style (this part is not required)
this.Focus();
}
else
{
//No focus
}
}
private void SelectedItemsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.UpdateUIWithSelectedItemsFromVm();
}
private void UpdateUIWithSelectedItemsFromVm()
{
if (!this.isSelectionChangeFromUI)
{
this.isSelectionChangeFromViewModel = true;
this.SelectedItems.Clear();
if (this.SelectedItemsList == null)
{
//Nothing to do, we just cleared all the selections
}
else
{
if (this.SelectedItemsList is IList iListFromVM)
foreach (var item in iListFromVM)
{
this.SelectedItems.Add(item);
}
}
this.isSelectionChangeFromViewModel = false;
}
else
{
//Nothing to do, the change is coming from the SelectionChanged event
}
}
private void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//If your collection allow suspension of notifications, it would be a good idea to add a check here in order to use it
if(!this.isSelectionChangeFromViewModel)
{
this.isSelectionChangeFromUI = true;
if (this.SelectedItemsList is IList iListFromVM)
{
iListFromVM.Clear();
foreach (var item in SelectedItems)
{
iListFromVM.Add(item);
}
}
else
{
throw new InvalidOperationException("The bound collection must inherit from IList");
}
this.isSelectionChangeFromUI = false;
}
else
{
//Nothing to do, the change is comming from the bound collection so no need to update it
}
}
private bool isSelectionChangeFromUI = false;
private bool isSelectionChangeFromViewModel = false;
public INotifyCollectionChanged SelectedItemsList
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register(nameof(SelectedItemsList),
typeof(INotifyCollectionChanged),
typeof(CustomDataGrid),
new PropertyMetadata(null));
}
You will have to initialize the DataGridSelectedItems earlier or you there will be a null exception when trying to subscribe to the collectionChanged event.
/// <summary>
/// I removed the notify property changed from your example as it probably isn't necessary unless you really intended to create a new Collection at some point instead of just clearing the items
/// (In this case you will have to adapt the code for the synchronization of CustomDataGrid so that it subscribe to the collectionChanged event of the new collection)
/// </summary>
public ObservableCollection<ExamplePersonModel> DataGridSelectedItems { get; set; } = new ObservableCollection<ExamplePersonModel>();
I didn't try all the edge cases but this should give you a good start and I added some directions as to how to improve it. Let me know if some parts of the code aren't clear and I will try to add some comments.

How to determine Panorama item index when selected panorama item is changed

I am building a panorama which displays images through binding. I need to find index of panorama item whenever the current item changes. But the SELECTIONCHANGED event is not firing in case when data is retrieved through binding. Can you please suggest some other way. Thanx in advance
XAML Code
<phone:Panorama x:Name="HeaderPanorama"
ItemsSource="{Binding PanoramaImages}"
Width="550" Margin="-10,-255,0,-140"
SelectionChanged="HeaderPanorama_SelectionChanged_1">
<phone:Panorama.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Margin="-10"/>
</DataTemplate>
</phone:Panorama.ItemTemplate>
</phone:Panorama>
CodeBehind
private void HeaderPanorama_SelectionChanged_1(
object sender,
SelectionChangedEventArgs e)
{
if (this.DataContext != null && this.DataContext is HomeViewModel)
{
((HomeViewModel)this.DataContext).PanoramaItemIndex =
HeaderPanorama.SelectedIndex;
}
}
ViewModel Code
public HomeViewModel()
{
RequestHomeData();
PanoramaImages = new List<string>();
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
}
private List<string> _panoramaImages;
public List<string> PanoramaImages
{
get { return _panoramaImages; }
set
{
_panoramaImages = value;
NotifyPropertyChanged("PanoramaImages");
}
}
private int _panoramaItemIndex;
public int PanoramaItemIndex
{
get { return _panoramaItemIndex; }
set
{
_panoramaItemIndex = value;
NotifyPropertyChanged("PanoramaItemIndex");
}
}

ListBox Map J and K Keys to Up/Down Arrow Keys

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);
}
}
}

Synchronizing a SelectedPath property with the SelectedItem in WPF's TreeView

I am trying to create a SelectedPath property (e.g. in my view-model) that is synchronized with a WPF TreeView. The theory is as follows:
Whenever the selected item in the tree view is changed (SelectedItem property/SelectedItemChanged event), update the SelectedPath property to store a string that represents the whole path to the selected tree node.
Whenever the SelectedPath property is changed, find the tree node indicated by the path string, expand the whole path to that tree node, and select it, after de-selecting the previously selected node.
In order to make all of this reproducible, let us assume that all tree nodes are of type DataNode (see below), that every tree node has a name that is unique among the children of its parent node, and that the path separator be a single forward slash /.
Updating the SelectedPath property in the SelectedItemChange event is not a problem - the following event handler works flawlessly:
void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
DataNode selNode = e.NewValue as DataNode;
if (selNode == null) {
vm.SelectedPath = null;
} else {
vm.SelectedPath = selNode.FullPath;
}
}
However, I fail to make the other way round work properly. Hence, my question, based on the generalized and minimized code sample below, is: How do I make WPF's TreeView respect my programmatical selection of items?
Now, how far have I come? First of all, TreeView's SelectedItem property is read-only, so it cannot be set directly. I have found and read numerous SO questions discussing this in-depth (such as this, this or this), and also resources on other sites, such as this blogpost, this article or this blogpost.
Almost all of these resources point to defining a style for TreeViewItem that binds TreeViewItem's IsSelected property to an equivalent property of the underlying tree node object from the view-model. Sometimes (e.g. here and here), the binding is made two-way, sometimes (e.g. here and here) it's a one-way binding. I don't see the point in making this a one-way-binding (if the tree view UI somehow deselects the item, that change should of course be reflected in the underlying view-model), so I have implemented the two-way version. (The same is usually suggested for IsExpanded, so I have also added a property for that.)
This is the TreeViewItem style I'm using:
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
I have confirmed that this style is actually applied (if I add a setter to set the Background property to Red, all the tree view items do appear with a red background).
And here is the simplified and generalized DataNode class:
public class DataNode : INotifyPropertyChanged
{
public DataNode(DataNode parent, string name)
{
this.parent = parent;
this.name = name;
}
private readonly DataNode parent;
private readonly string name;
public string Name {
get {
return name;
}
}
public override string ToString()
{
return name;
}
public string FullPath {
get {
if (parent != null) {
return parent.FullPath + "/" + name;
} else {
return "/" + name;
}
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) {
PropertyChanged(this, e);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private DataNode[] children;
public IEnumerable<DataNode> Children {
get {
if (children == null) {
children = DataSource.GetChildNodes(FullPath).Select(s => new DataNode(this, s)).ToArray();
}
return children;
}
}
private bool isSelected;
public bool IsSelected {
get {
return isSelected;
}
set {
if (isSelected != value) {
isSelected = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsSelected"));
}
}
}
private bool isExpanded;
public bool IsExpanded {
get {
return isExpanded;
}
set {
if (isExpanded != value) {
isExpanded = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsExpanded"));
}
}
}
public void ExpandPath()
{
if (parent != null) {
parent.ExpandPath();
}
IsExpanded = true;
}
}
As you can see, each node has a name, a reference to its parent node (if any), it initializes its child nodes lazily, but only once, and it has an IsSelected and an IsExpanded property, both of which trigger the PropertyChanged event from the INotifyPropertyChanged interface.
So, in my view-model, the SelectedPath property is implemented as follows:
public string SelectedPath {
get {
return selectedPath;
}
set {
if (selectedPath != value) {
DataNode prevSel = NodeByPath(selectedPath);
if (prevSel != null) {
prevSel.IsSelected = false;
}
selectedPath = value;
DataNode newSel = NodeByPath(selectedPath);
if (newSel != null) {
newSel.ExpandPath();
newSel.IsSelected = true;
}
OnPropertyChanged(new PropertyChangedEventArgs("SelectedPath"));
}
}
}
The NodeByPath method correctly (I've checked this) retrieves the DataNode instance for any given path string. Nonetheless, I can run my application and see the following behavior, when binding a TextBox to the SelectedPath property of the view-model:
type /0 => item /0 is selected and expanded
type /0/1/2 => item /0 remains selected, but item /0/1/2 gets expanded.
Similarly, when I first set the selected path to /0/1, that item gets correctly selected and expanded, but for any subsequent path values, the items only get expanded, never selected.
After debugging for a while, I thought the problem was a recursive call of the SelectedPath setter in the prevSel.IsSelected = false; line, but adding a flag that would prevent the execution of the setter code while that command is being executed did not seem to change the behaviour of the programme at all.
So, what am I doing wrong here? I don't see where I'm doing something different than what is suggested in all of those blogposts. Does the TreeView need to be notified somehow about the new IsSelected value of the newly selected item?
For your convencience, the full code of all 5 files that constitute the self-contained, minimal example (the data source obviously returns bogus data in this example, yet it returns a constant tree and hence makes the test cases indicated above reproducible):
DataNode.cs
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
namespace TreeViewTest
{
public class DataNode : INotifyPropertyChanged
{
public DataNode(DataNode parent, string name)
{
this.parent = parent;
this.name = name;
}
private readonly DataNode parent;
private readonly string name;
public string Name {
get {
return name;
}
}
public override string ToString()
{
return name;
}
public string FullPath {
get {
if (parent != null) {
return parent.FullPath + "/" + name;
} else {
return "/" + name;
}
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) {
PropertyChanged(this, e);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private DataNode[] children;
public IEnumerable<DataNode> Children {
get {
if (children == null) {
children = DataSource.GetChildNodes(FullPath).Select(s => new DataNode(this, s)).ToArray();
}
return children;
}
}
private bool isSelected;
public bool IsSelected {
get {
return isSelected;
}
set {
if (isSelected != value) {
isSelected = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsSelected"));
}
}
}
private bool isExpanded;
public bool IsExpanded {
get {
return isExpanded;
}
set {
if (isExpanded != value) {
isExpanded = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsExpanded"));
}
}
}
public void ExpandPath()
{
if (parent != null) {
parent.ExpandPath();
}
IsExpanded = true;
}
}
}
DataSource.cs
using System;
using System.Collections.Generic;
namespace TreeViewTest
{
public static class DataSource
{
public static IEnumerable<string> GetChildNodes(string path)
{
if (path.Length < 40) {
for (int i = 0; i < path.Length + 2; i++) {
yield return (2 * i).ToString();
yield return (2 * i + 1).ToString();
}
}
}
}
}
ViewModel.cs
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
namespace TreeViewTest
{
public class ViewModel : INotifyPropertyChanged
{
private readonly DataNode[] rootNodes = DataSource.GetChildNodes("").Select(s => new DataNode(null, s)).ToArray();
public IEnumerable<DataNode> RootNodes {
get {
return rootNodes;
}
}
private DataNode NodeByPath(string path)
{
if (path == null) {
return null;
} else {
string[] levels = selectedPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
IEnumerable<DataNode> currentAvailable = rootNodes;
for (int i = 0; i < levels.Length; i++) {
string node = levels[i];
foreach (DataNode next in currentAvailable) {
if (next.Name == node) {
if (i == levels.Length - 1) {
return next;
} else {
currentAvailable = next.Children;
}
break;
}
}
}
return null;
}
}
private string selectedPath;
public string SelectedPath {
get {
return selectedPath;
}
set {
if (selectedPath != value) {
DataNode prevSel = NodeByPath(selectedPath);
if (prevSel != null) {
prevSel.IsSelected = false;
}
selectedPath = value;
DataNode newSel = NodeByPath(selectedPath);
if (newSel != null) {
newSel.ExpandPath();
newSel.IsSelected = true;
}
OnPropertyChanged(new PropertyChangedEventArgs("SelectedPath"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) {
PropertyChanged(this, e);
}
}
}
}
Window1.xaml
<Window x:Class="TreeViewTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TreeViewTest" Height="450" Width="600"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TreeView ItemsSource="{Binding RootNodes}" SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding .}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<TextBox Grid.Row="1" Text="{Binding SelectedPath, Mode=TwoWay}"/>
</Grid>
</Window>
Window1.xaml.cs
using System;
using System.Windows;
namespace TreeViewTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = vm;
}
void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
DataNode selNode = e.NewValue as DataNode;
if (selNode == null) {
vm.SelectedPath = null;
} else {
vm.SelectedPath = selNode.FullPath;
}
}
private readonly ViewModel vm = new ViewModel();
}
}
I could not reproduce the behavior you described. There is a problem with the code you posted unrelated to TreeView. The TextBox default UpdateSourceTrigger is LostFocus therefore the TreeView is affected only after the TextBox loses focus but there are only two controls in your example so to make the TextBox lose focus you have to select something in the TreeView (then the entire selection process is messed up).
What I did was to add a button at the bottom of the form. The button does nothing but when clicked the TextBox loses focus. Everything works perfectly now.
I compiled it in VS2012 using .Net 4.5

RadioButton IsChecked loses binding

I'm triying to bind to a RadioButton.IsChecked property, and it only works once. After that, the binding doesn't work anyore, and I have no idea why this happens. Can anyone help out with this? Thanks!
This is my code.
C#
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
private bool _isChecked1 = true;
public bool IsChecked1
{
get { return _isChecked1; }
set
{
if (_isChecked1 != value)
{
_isChecked1 = value;
}
}
}
private bool _isChecked2;
public bool IsChecked2
{
get { return _isChecked2; }
set
{
if (_isChecked2 != value)
{
_isChecked2 = value;
}
}
}
}
XAML:
<Grid>
<StackPanel>
<RadioButton Content="RadioButton1" IsChecked="{Binding IsChecked1}" />
<RadioButton Content="RadioButton2" IsChecked="{Binding IsChecked2}" />
</StackPanel>
</Grid>
It's an unfortunate known bug. I'm assuming this has been fixed in WPF 4.0 given the new DependencyObject.SetCurrentValue API, but have not verified.
Here is a working solution: http://pstaev.blogspot.com/2008/10/binding-ischecked-property-of.html. It's a shame that Microsoft didn't correct this error.
Just a follow-up to Kent's answer here...this has in fact been fixed in WPF 4.0., I'm leveraging this behavior in my current project. The radio button that is de-activated now gets its binding value set to false, rather than breaking the binding.
I guess you need to implement the INotifyPropertyChanged interface
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private bool _isChecked1 = true;
public bool IsChecked1
{
get { return _isChecked1; }
set
{
if (_isChecked1 != value)
{
_isChecked1 = value;
NotifyPropertyChanged("IsChecked1");
}
}
} // and the other property...
:)

Resources