How do I manually refresh a databinding? - wpf

I have big query based datamodel, and I wish to display results of Linq queries into grids.
The GUI will edit attributes, which will affect the query result. However, even though the binding executes just fine, the debugger shows no subscriber to the PropertyChanged event (it is "null"). I have made this test example.
I wish for the user to set a bunch of criteria and then hit an "execute" button. In my example, I expected the number of items in the grid to change.
Here is the xaml:
<Window x:Class="GridViewNotifyTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<Button Click="Button_Click">Take 3</Button>
<Button Click="Button_Click_1">Take 5</Button>
<Button Click="Button_Click_2">FireNotify</Button>
<DataGrid ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding}"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Grid>
</Window>
And here is the C#:
namespace GridViewNotifyTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window,INotifyPropertyChanged
{
private int _takeAmount;
public MainWindow()
{
InitializeComponent();
_takeAmount = 4;
DataContext = Amount;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_takeAmount = 3;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
_takeAmount = 5;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
OnPropertyValueChanged("Amount");
}
protected virtual void OnPropertyValueChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); // THE DEBUGGER SHOWS THE PROPERTYCHANGED DELEGATE AS NULL.
}
public IEnumerable<int> Amount
{
get { return Enumerable.Range(1,10).Take(_takeAmount); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

According to question title, fast answer will be to use BindingExpression.UpdateTarget method.

Set the DataContext to this and then change your Binding to be
<DataGrid ItemsSource="{Binding Amount}">

Related

Caliburn Micro MVVM : How to subscribe and unsubscribe event in ViewModel?

I created a WPF sample (using caliburn micro with MVVM pattern, no code-behind) with a view model and their related views:
ShellView.xaml and ShellViewModel.cs
The ShellView contains:
A ComobBox, which contains a list of string, if this combox selection is changed, it will raise comboBox1_SelectionChanged() in ShellViewModel.
A Button, if click this button, it will raise Button1_Click() to delete the first item of list in ShellViewModel.
My questions:
If I want to click the button without trigger comboBox1_SelectionChanged in view model, how to do that?
If it implemented in code-behind, I can do like this:
public void Button1_Click(object sender, EventArgs e)
{
comboBox1.SelectionChanged -= comboBox1_SelectionChanged;
MyCollection.RemoveAt(0);
comboBox1.SelectionChanged += comboBox1_SelectionChanged;
}
I have no idea how to achieve this in view model. The following is the code:
ShellView.xaml
<UserControl x:Class="WpfApp.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp.Views"
xmlns:cal="http://caliburnmicro.com"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=" auto"/>
<RowDefinition Height=" auto"/>
</Grid.RowDefinitions>
<ComboBox Name="comboBox1" Grid.Row="0" ItemsSource="{Binding MyCollection}" SelectedValue="{Binding SelectMyListValue}"
cal:Message.Attach="[Event SelectionChanged]=[Action comboBox1_SelectionChanged($source,$eventArgs)]" />
<Button Name="Button1" Grid.Row="1" Content="Delete"
cal:Message.Attach="[Event Click]=[Action Button1_Click($source,$eventArgs)]" />
</Grid>
</UserControl>
ShellViewModel.cs
using Caliburn.Micro;
using System;
using System.Windows.Controls;
namespace WpfApp.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.OneActive
{
private BindableCollection<string> _myCollection = new BindableCollection<string>() { "item1", "item2"};
public BindableCollection<string> MyCollection
{
get => _myCollection;
set
{
_myCollection = value;
NotifyOfPropertyChange(() => MyCollection);
}
}
private string _selectMyListValue = "item1";
public string SelectMyListValue
{
get => _selectMyListValue;
set
{
_selectMyListValue = value;
NotifyOfPropertyChange(nameof(SelectMyListValue));
}
}
public void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Do something...
}
public void Button1_Click(object sender, EventArgs e)
{
MyCollection.RemoveAt(0);
}
}
}
Thank you in advance.
Your requirement can't be fully met, as when you remove the selected item from the collection a change of SelectedValue (to null) is inevitable.
Furthermore: You don't need to bind to the SelectionChanged event. You already have a binding to SelectedValue, so the setter of the bound property is called when the selection changes. This doesn't happen, when you remove a value from the collection that is not currently selected.
I would also recommend not to subscribe to the Clicked event of the button, but to bind an ICommand (added to your viewmodel) to the Command property of the button. An easy to use implementation would be the RelayCommand from the Windows Community Toolkit. You can read about it here: https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/relaycommand. It also isn't difficult to implemnt a version on your own, if you don't want to use the whole toolkit.
Code sample:
public class RelayCommand : ICommand
{
private readonly Action<object?> execute;
private readonly Func<object?, bool> canExecute;
public RelayCommand(
Action<object?> execute,
Func<object?, bool>? canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute ?? (_ => true);
}
public bool CanExecute(object? parameter) => this.canExecute(parameter);
public void Execute(object? parameter)
{
this.execute(parameter);
}
public event EventHandler? CanExecuteChanged;
}
// on your viewmodel add...
public ICommand RemoveFirstItemCommand { get; set; }
private void RemoveFirstItem(object? param)
{
if (this.Items.Count > 0)
{
this.Items.RemoveAt(0);
}
}
// ...and in the constructor init the command
this.RemoveFirstItemCommand = new RelayCommand(this.RemoveFirstItem);
I got a solution which achieved the goal, but I'm not sure if it's the right way.
There is a "Microsoft.Xaml.Behaviors" which provided "Interaction.Triggers" that contains "ComparisonCondition". I can use it to bind a value to determine the EventCommand is raised or not.
I updated the code as following:
ShellViewModel.cs
using Caliburn.Micro;
using System;
using System.Windows.Controls;
using WpfApp.Commands;
namespace WpfApp.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.OneActive
{
private bool _IsEnableSelectionChangedCommand = true;
public bool IsEnableSelectionChangedCommand
{
get => _IsEnableSelectionChangedCommand;
set
{
_IsEnableSelectionChangedCommand = value;
NotifyOfPropertyChange(() => IsEnableSelectionChangedCommand);
}
}
private BindableCollection<string> _myCollection = new BindableCollection<string>() { "item1", "item2"};
public BindableCollection<string> MyCollection
{
get => _myCollection;
set
{
_myCollection = value;
NotifyOfPropertyChange(() => MyCollection);
}
}
private string _selectMyListValue = "item1";
public DelegateCommand<object> DoSelectionChangedCommand { get; }
public ShellViewModel()
{
DoSelectionChangedCommand = new DelegateCommand<object>(comboBox1_SelectionChanged, CanExecute);
}
private bool CanExecute(object param)
{
return true;
}
private void comboBox1_SelectionChanged(object param)
{
SelectionChangedEventArgs e = param as SelectionChangedEventArgs;
ComboBox item = e.Source as ComboBox;
// Do something...
}
public string SelectMyListValue
{
get => _selectMyListValue;
set
{
_selectMyListValue = value;
NotifyOfPropertyChange(nameof(SelectMyListValue));
}
}
public void Button1_Click(object sender, EventArgs e)
{
IsEnableSelectionChangedCommand = false;
MyCollection.RemoveAt(0);
IsEnableSelectionChangedCommand = true;
}
}
}
ShellView.xaml
<UserControl x:Class="WpfApp.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cal="http://caliburnmicro.com"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:cmd="clr-namespace:WpfApp.Commands"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=" auto"/>
<RowDefinition Height=" auto"/>
</Grid.RowDefinitions>
<ComboBox Name="comboBox1" Grid.Row="0" ItemsSource="{Binding MyCollection}" SelectedValue="{Binding SelectMyListValue}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventCommand Command="{Binding DoSelectionChangedCommand}" />
<i:Interaction.Behaviors>
<i:ConditionBehavior>
<i:ConditionalExpression>
<i:ComparisonCondition LeftOperand= "{Binding IsEnableSelectionChangedCommand}" Operator="Equal" RightOperand="True"/>
</i:ConditionalExpression>
</i:ConditionBehavior>
</i:Interaction.Behaviors>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Button Name="Button1" Grid.Row="1" Content="Delete"
cal:Message.Attach="[Event Click]=[Action Button1_Click($source,$eventArgs)]" />
</Grid>
</UserControl>

My static Model/Property is not binding to my UI

New to WPF.
I am trying to bind my Model to my UI. So, when the Property is changed during my User actions I want the field to update whereever it occurs on my UI.
This is my Model:
namespace WpfApplication1
{
public class model2
{
private static string myField2;
public static string MyField2
{
get { return myField2; }
set { myField2 = value; }
}
}
}
My Markup:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:model2 x:Key="mymodel"/>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Source={StaticResource ResourceKey=mymodel}, Path=MyField2}"></TextBlock>
<Button Content="static test!" Click="Button_Click_1" />
</StackPanel>
</Grid>
</Window>
My code behind:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
model2.MyField2 = "static!";
}
}
}
The field on the UI does not change?
You need to notify changes to the UI so it can update with new values.
In your case you want to notify static properties of changes so you would need a static event. The problem is the INotifyPropertyChanged interface needs a member event so you won't be able to go that way.
You best shot is to implement the Singleton pattern:
namespace WpfApplication1
{
public class model2 : INotifyPropertyChanged
{
//private ctor so you need to use the Instance prop
private model2() {}
private string myField2;
public string MyField2
{
get { return myField2; }
set {
myField2 = value;
OnPropertyChanged("MyField2");
}
}
private static model2 _instance;
public static model2 Instance {
get {return _instance ?? (_instance = new model2();)}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then make your property a member property and bind like this:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:model2 x:Key="mymodel"/>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Source={x:Static local:model2.Instance}, Path=MyField2}"/>
<Button Content="static test!" Click="Button_Click_1" />
</StackPanel>
</Grid>
</Window>
Code behind:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
model2.Instance.MyField2 = "static!";
}
}
}
Use the Static extension to bind the TextBlocks Text Property:
<TextBlock Text="{Binding Source={Static MyModel.MyField2}, Mode=TwoWay">
But still the Property must raise the PropertyChanged event. My understanding why you use the static field is to be able to set the value from somewhere else. Have you thougt about using messages instead? Checkout the MVVM Light toolkit and the messenger. This would decouple the two components
I think that static properties are not what you want to use, from comments I can deduce that you are using only to make your program work. Below is the full working code.
App.xaml
Remove the code StartupUri="MainWindow.xaml instead we will instantiate MainWindow in code-behind to provide DataContext.
App.xaml.cs
Here we are assigning object of Model2 as Window.DataContext and then showing the window.
public partial class App : Application
{
public App()
{
Model2 model = new Model2();
MainWindow window = new MainWindow();
window.DataContext = model;
window.Show();
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
//We get hold of `DataContext` object
var model = this.DataContext as Model2;
model.MyField2 = "Hello World";
}
}
Model:
public class Model2 : INotifyPropertyChanged
{
private string _myField2;
public string MyField2
{
get { return _myField2; }
set
{
_myField2 = value;
OnPropertyChanged("MyField2");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainWindow.xaml
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding MyField2}"></TextBlock>
<Button Content="static test!" Click="ButtonBase_OnClick" />
</StackPanel>
</Grid>
</Window>
And I checked, it works !

DataBinding to a static singleton

I have a problem binding to a WPF form . I have my own static "settings" class (singleton) that implements PropertyChangedEventHandler and raises the event whenever a property is updated.
The singleton object is added to resources in the form's constructor and the property is correctly read on form's initialization, thus suggesting that the binding is correct.
However, WPF does NOT register any event handler for PropertyChangedEventHandler and PropertyChanged is always null. Thus the event is never raised, and my form is never updated (it's meant to be updated on a button click).
What am I doing wrong?
I suspect that calling Resources.Add for some reason prevents WPF from registering its own event handler, but I'm not sure.
I've read multiple SO questions on similar topics, but the 2 most common issues are not creating a proper singleton (thus passing another instance to xaml then intended) or not implementing INotifyPropertyChanged. I'm doing both of these correctly.
Expected behavior:
Settings.TextValue is the property I'm interested in. In its setter, NotifyPropertyChanged is called, which unfortunately fails to raise this.PropertyChanged event, since WPF registers no handler.
When MainWindow.Button1 is click, the textBox's value is supposed to change to "ButtonA OK" from the initial value of Settings.TextBox ("testOK").
Here's the code:
Settings.cs:
namespace bindings
{
public sealed class Settings : INotifyPropertyChanged
{
private static readonly Settings instance = new Settings();
private Settings()
{
}
public static Settings Instance { get { return instance; } }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = null)
{
// passing propertyName=null raises the event for all properties
if (PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string textValue = "testOK";
public static string TextValue
{
get { return Instance.textValue; }
set { Instance.textValue = value; Instance.NotifyPropertyChanged(); }
}
}
MainWindow.xaml.cs
namespace bindings
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
Resources.Add("foobar", Settings.Instance);
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
private void button1_Click(object sender, RoutedEventArgs e)
{
int hash = Settings.Instance.GetHashCode();
Settings.TextValue = "ButtonA OK";
}
}
}
MainWindow.xaml
<Window x:Class="bindings.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" WindowStyle="ToolWindow">
<Grid PresentationTraceSources.TraceLevel="High" DataContext="{StaticResource foobar}">
<Button Content="ButtonA" Height="33" HorizontalAlignment="Left" Margin="76,243,0,0" Name="button1" VerticalAlignment="Top" Width="101" Click="button1_Click" />
<TextBox Height="28" HorizontalAlignment="Left" Margin="182,180,0,0" Name="textBox1" VerticalAlignment="Top" Width="93"
Text="{Binding Path=TextValue, Mode=OneWay}" DataContext="{Binding}" PresentationTraceSources.TraceLevel="High"/>
</Grid>
</Window>
Thanks for help!

WPF Grid Updating Issue

I am adding an item to my datasource that is bound to a datagrid, but the UI is not updating to show the new item. Here is my code.
DBContainer ctx = new DBContainer();
private void btnAddNewDesignator_Click(object sender, RoutedEventArgs e)
{
DESIGNATOR a = new DESIGNATOR();
a.DesignatorName = txtDesignator.Text;
a.TXFreq = txtTX.Text;
a.RXFreq = txtRX.Text;
ctx.AddToDESIGNATORs(a);
ctx.SaveChanges();
dgDesignators.Items.Refresh();
}
private void btnAddNewDesignator_Loaded(object sender, RoutedEventArgs e)
{
dgDesignators.ItemsSource = ctx.DESIGNATORs;
}
Here is my xaml:
<DataGrid AutoGenerateColumns="False" Height="225" HorizontalAlignment="Left" Margin="56,230,0,0" Name="dgDesignators" VerticalAlignment="Top" Width="602" CanUserAddRows="True" CanUserDeleteRows="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Designator" Binding="{Binding Path=DesignatorName,Mode=OneWay}" />
<DataGridTextColumn Header="TX Frequency" Binding="{Binding Path=TXFreq,Mode=OneWay}" />
<DataGridTextColumn Header="RX Frequency" Binding="{Binding Path=RXFreq,Mode=OneWay}" />
<DataGridTextColumn Header="In Use" />
</DataGrid.Columns>
</DataGrid>
As you can see I am refreshing, I have also tried setting the ItemSource to null first, and then rebinding it, but to no avail. Thanks for your help.
DBContainer ctx = new DBContainer();
private void btnAddNewDesignator_Click(object sender, RoutedEventArgs e)
{
DESIGNATOR a = new DESIGNATOR();
a.DesignatorName = txtDesignator.Text;
a.TXFreq = txtTX.Text;
a.RXFreq = txtRX.Text;
ctx.AddToDESIGNATORs(a);
ctx.SaveChanges();
dgDesignators.Items.Refresh();
}
private void btnAddNewDesignator_Loaded(object sender, RoutedEventArgs e)
{
dgDesignators.ItemsSource = ctx.DESIGNATORs.ToList();
}
I don't suggest this kind of code in WPF, try doing it in an MVVM pattern and it should be easier to implement and all you need to do is make a class that will act as the DataContext of the DataGrid and make that class implement INotifyPropertyChange
Button Event Handler:
private void btnAddNewDesignator_Click(object sender, RoutedEventArgs e)
{
DESIGNATOR a = new DESIGNATOR();
a.DesignatorName = txtDesignator.Text;
a.TXFreq = txtTX.Text;
a.RXFreq = txtRX.Text;
ctx.AddToDESIGNATORs(a);
ctx.SaveChanges();
designatorDataSource = ctx.DESIGNATOR.ToList();
}
ItemsSourceProperty:
private List<DESIGNATOR> _designatorDataSource = new List<DESIGNATOR>();
public List<DESIGNATOR designatorDataSource {
get {
return _designatorDataSource;
}
set {
_designatorDataSource = value;
OnPropertyChanged("designatorDataSource");
}
}
Then you bind your DataGrid:
<DataGrid Name="dgDesignators" ItemsSource={Binding designatorDataSource, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
</DataGrid>
Edit:
Adding INotifyPropertyChanged, have your class extend INotifyPropertyChanged.
Example:
using System.ComponentModel;
public class MyClass : Window, INotifyPropertyChanged
Add the Property Change Event
#region Property Change Event
/// <summary>
/// Occurs when a property is changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the <see cref="PropertyChanged"/> for a given
/// property.
/// </summary>
/// <param name="propertyName"></param>
protected void OnPropertyChanged(String propertyName) {
// Get the handler
PropertyChangedEventHandler handler = this.PropertyChanged;
// Check that the event handler is not null
if(null != handler) {
// Fire the event
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion

DataContextChanged of a Tab in a TabControl is raised too early

I have a TabControl binding to some items. Underneath it is a Button where I can add items dynamically. On adding an item, the new item should become the active Tab (works fine with TabControl.SelectedItem):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TabControl ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=OneWay}">
<TabControl.ContentTemplate>
<DataTemplate>
<this:UserControl1 />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<Button Content="Foo" Click="Button_Click"/>
</StackPanel>
</Window>
Code-behind:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : INotifyPropertyChanged
{
public ObservableCollection<Foo> Items { get; set; }
public Foo SelectedItem { get { return Items.Last(); } }
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
Items = new ObservableCollection<Foo>();
Items.Add(new Foo {Bar = "bar"});
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Items.Add(new Foo {Bar = "bar"});
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Items"));
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
}
public class Foo { public string Bar { get; set; } }
}
The UserControl1 looks like this:
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBox/>
<TextBox x:Name="_textBox"
DataContextChanged="OnDataContextChanged"
Text="{Binding Bar}" />
</StackPanel>
</UserControl>
And the code-behind of it should focus _textBox and selectAll its text when the user clicks on the tab:
using System.Windows;
namespace WpfApplication1
{
public partial class UserControl1
{
public UserControl1()
{
InitializeComponent();
}
private void OnDataContextChanged(object sender,
DependencyPropertyChangedEventArgs e)
{
_textBox.Focus();
_textBox.SelectAll();
}
}
}
I try to achieve that with the DataContextChanged-event, but due to its unpredictability (s.f. http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.datacontextchanged.aspx), it doesn't work all the time. I also tried it with the Loaded-event, but this will be called only once when the DataTemplate is loaded.
So, I think I need to receive the Loaded-event every time the DataContext has changed and the data-binding engine has finished its job. Is there such an event?
Are you wanting to select the text when the user adds a tab AND when the user clicks on a different tab?
If this is the case you may want to to handle this with two event handlers - The tab changed event for the tab control - and then setting it in code when you add a new item.
The DataContext according to your code does not change. It is set to the main window and then inherited down to the child controls.
public MainWindow()
{
Items = new ObservableCollection<Foo>();
Items.Add(new Foo {Bar = "bar"});
InitializeComponent();
DataContext = this;
}

Resources