I have a combobox whose SelectedItem and ItemSource properties are bound to the viewmodel. The objects in the ItemSource are time sensitive in that the objects expire and I can only set active items in the collection to the ItemSource. Now at a certain point, the selectedItem object maynot be in the ItemSource.
I think the normal behaviour of the ItemSource is to only allow objects to be selected from the ItemSource Collection but in this case I do want to select an object which is no longer in the ItemSource.
How can I implement this behaviour? I will post some code here to support my problem.
Window.xaml
<Window x:Class="ExpiredSelectedItem.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>
<ComboBox Height="23"
HorizontalAlignment="Left"
Margin="184,68,0,0"
Name="comboBox1"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding CustomList}"
SelectedItem="{Binding ActiveItem}"
SelectedValue="Code"
SelectedValuePath="{Binding ActiveItem.Code}"
DisplayMemberPath="Code"
/>
</Grid>
Window.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ExpiredSelectedItem
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CustomList = new List<DomainObject>();
CustomList.AddRange(new DomainObject[] {
new DomainObject() { Code = "A", IsActive =true},
new DomainObject() { Code ="B", IsActive = true},
new DomainObject() { Code = "C", IsActive =true},
});
ActiveItem = new DomainObject() { Code = "D", IsActive = false };
this.DataContext = this;
}
public List<DomainObject> CustomList { get; set; }
public DomainObject ActiveItem { get; set; }
}
public class DomainObject
{
public string Code { get; set; }
public bool IsActive { get; set; }
}
}
Even though I select the code D in the code-behind, when the combobox loads the first item (A) is selected. Expected behaviour was that "D" should have been selected in the textbox but no items should be when the dropdown is opened.
I don't know if this plays any role, but it sure looks suspicious. Try removing it:
<ComboBox ... SelectedValue="Code" ...
To keep the selected item when it is not in the list you can to do the following:
Prevent the ActiveItem from getting set to NULL; see: if (value != null)
Clear the ComboBox; see: comboBox1.SelectedIndex = -1;
Implement INotifyPropertyChanged (which you probably already do)
These changes might cause your code more harm than good, but hopefully this gets you started.
Here is the code-behind:
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace ComboBoxSelectedItem
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public MainWindow()
{
InitializeComponent();
CustomList = new List<DomainObject>();
CustomList.AddRange(new DomainObject[] {
new DomainObject() { Code = "A", IsActive =true},
new DomainObject() { Code ="B", IsActive = true},
new DomainObject() { Code = "C", IsActive =true},
});
this.DataContext = this;
}
public List<DomainObject> CustomList { get; set; }
private DomainObject _activeItem = new DomainObject() { Code = "D", IsActive = false };
public DomainObject ActiveItem
{
get { return _activeItem; }
set
{
if (value != null)
{
_activeItem = value;
}
Notify("ActiveItem");
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ActiveItem = new DomainObject() { Code = "D", IsActive = false };
comboBox1.SelectedIndex = -1;
}
}
}
Here is the XAML:
<Window x:Class="ComboBoxSelectedItem.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>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
Height="23"
HorizontalAlignment="Left"
Name="comboBox1"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding CustomList}"
SelectedItem="{Binding ActiveItem}"
DisplayMemberPath="Code"/>
<TextBox
Grid.Row="1"
Text="{Binding Path=ActiveItem.Code, Mode=TwoWay}"/>
<Button
Grid.Row="2"
Content="D"
Click="Button_Click"/>
</Grid>
</Window>
Related
I am experimenting with WPF and MVVM.
My ViewModel contains a custom collection (IEnumerable) which has to be rendered in UI. I have added some code to add a new entity to the Model which I suppose to be rendered in UI. But that does not happen.
[1] Am I wrong with the pattern? [2] Why isn't the new entity is not reflected in the View, though I raise INPC?
Please find my code below
Classes.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _06_MVVMTest___Add_to_Model_Reflects_in_View
{
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int Salary { get; set; }
public Employee(int id, string name,int age, int salary)
{
this.Id = id;
this.Name = name;
this.Age = age;
this.Salary = salary;
}
}
public class EmployeeCollection : IEnumerable
{
List<Employee> employees = new List<Employee>();
public EmployeeCollection()
{
employees.Add(new Employee(100, "Alice", 23, 300));
employees.Add(new Employee(100, "Bob", 22, 400));
employees.Add(new Employee(100, "Trude", 21, 200));
}
public IEnumerator GetEnumerator()
{
foreach (Employee emp in employees)
{
if (emp==null)
{
break;
}
yield return emp;
}
}
public void AddNewEmployee()//For Test
{
employees.Add(new Employee(200, "Dave", 21, 2000));
}
}
public class EmployeeViewModel:INotifyPropertyChanged
{
EmployeeCollection employees=new EmployeeCollection();
public EmployeeCollection Employees
{
get { return employees; }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyOnEmployeeCollectionChanged()
{
if (PropertyChanged!=null)
PropertyChanged(this,new PropertyChangedEventArgs("Employees"));
}
}
}
MainWindow.xaml.cs
sing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace _06_MVVMTest___Add_to_Model_Reflects_in_View
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
EmployeeViewModel vm =(EmployeeViewModel) this.DataContext;
vm.Employees.AddNewEmployee();
vm.NotifyOnEmployeeCollectionChanged();
}
}
}
MainWindow.xaml
<Window x:Class="_06_MVVMTest___Add_to_Model_Reflects_in_View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:_06_MVVMTest___Add_to_Model_Reflects_in_View"
Title="Employee Window" Height="350" Width="525">
<Window.DataContext>
<local:EmployeeViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView Height="300" Grid.Row="0" Grid.Column="0" BorderBrush="Blue" ItemsSource="{Binding Employees}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Background="LightCoral" Margin="10,10,10,10">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Height="300" Grid.Row="0" Grid.Column="1" BorderBrush="Blue" ItemsSource="{Binding Employees}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Background="LightCoral" Margin="10,10,10,10">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="1" Height="40" Content="add new employee" Click="Button_Click" />
</Grid>
</Window>
There is a problem with the way WPF handles property changes along with your way of notifying the change. WPF checks to see if your value is still the same; the EmployeeCollection is actually the same as the old one (it is still the same instance and it won't look inside the collection).
One solution is to change the implementation of EmployeeCollection to this:
public class EmployeeCollection : IEnumerable, INotifyCollectionChanged
{
private ObservableCollection<Employee> employees = new ObservableCollection<Employee>();
public EmployeeCollection()
{
employees.Add(new Employee(100, "Alice", 23, 300));
employees.Add(new Employee(100, "Bob", 22, 400));
employees.Add(new Employee(100, "Trude", 21, 200));
}
public IEnumerator GetEnumerator()
{
return employees.GetEnumerator();
}
public void AddNewEmployee() //For Test
{
employees.Add(new Employee(200, "Dave", 21, 2000));
}
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add { employees.CollectionChanged += value; }
remove { employees.CollectionChanged -= value; }
}
}
This introduces the INotifyCollectionChanged interface and removes the need to call vm.NotifyOnEmployeeCollectionChanged() in the main window, since the ObservableCollection<T> underlying the EmployeeCollection will tell WPF that it has changed.
If you do not want to learn about these concepts for now, a quick work around would be to :
public void NotifyOnEmployeeCollectionChanged()
{
var current = this.employees;
this.employees = null;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
this.employees = current;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
}
Your custom collection should implement INotifyCollectionChanged. Every time you add an item, fire the CollectionChanged event.
public class EmployeeCollection : IEnumerable, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void AddNewEmployee() //For Test
{
employees.Add(new Employee(200, "Dave", 21, 2000));
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
}
I would like to set a DataGrid.ItemsSource property according a ListBox.SelectedItem value so that whenever a category is selected in the ListBox, the DataGrid should display the appropriate collection.
Example :
ListBox has the following items :
Artists
Medias
(taken from this enumeration)
public enum Category
{
Artists,
Medias
}
Then the DataGrid should pick the associated collection from here :
public class MyCategories
{
public IEnumerable<Media> Medias { get; set; }
public IEnumerable<Artist> Artists { get; set; }
}
However, ConverterParameter of DataGrid.ItemsSource does not allow a binding to be defined for it.
A 'Binding' cannot be set on the 'ConverterParameter' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
<DataGrid ItemsSource="{Binding Mode=OneWay,
Converter={StaticResource categoriesConverter},
ConverterParameter={Binding ElementName=listBox1, Path=SelectedItem}}" />
How would one achieve this ?
One way is to create a class with both and name and an IEnumerable
In your case the IEnumerable is a class (not a string) and in the path you will need to include the name of the property to display
<Window x:Class="Waster13.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>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Path=EnumsPlus}" DisplayMemberPath="Name" x:Name="lbMaster"/>
<ListBox Grid.Column="1" ItemsSource="{Binding ElementName=lbMaster, Path=SelectedItem.IEnum}" />
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections;
namespace Waster13
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private List<EnumPlus> enumsPlus = new List<EnumPlus>();
public MainWindow()
{
this.DataContext = this;
enumsPlus.Add(new Media("media1", new List<string>() { "m1", "m2" }));
enumsPlus.Add(new Media("media2", new List<string>() { "m3", "m4" }));
enumsPlus.Add(new Artist("artist1", new List<string>() { "a1", "a2" }));
enumsPlus.Add(new Artist("artist2", new List<string>() { "a3", "a4" }));
InitializeComponent();
}
public List<EnumPlus> EnumsPlus { get { return enumsPlus; } }
}
public interface EnumPlus : IEnumerable
{
String Name { get; }
}
public class Media : EnumPlus
{
private string name;
private IEnumerable iEnum;
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)iEnum.GetEnumerator();
}
public String Name { get { return name; } }
public IEnumerable IEnum { get { return iEnum; } }
public Media(string Name, IEnumerable IEnum)
{ name = Name; iEnum = IEnum; }
}
public class Artist : EnumPlus
{
private string name;
private IEnumerable iEnum;
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)iEnum.GetEnumerator();
}
public String Name { get { return name; } }
public IEnumerable IEnum { get { return iEnum; } }
public Artist(string Name, IEnumerable IEnum)
{ name = Name; iEnum = IEnum; }
}
}
I am using the WPF AutoCompleteBox and I have it working great, but one thing I would like to do is sort the suggestion list on the fly after each letter is entered into the primary TextBox. Does anyone know how to do this? I tried using an ICollectionView property with the DefaultView logic and adding SortDescriptions but it doesn't seem to phase the suggestion list. To make sure my collection view sorting was working I put a normal ListBox control and an AutoCompleteBox control on the same window and bound both controls to the same observable collection with the same collection view and the normal ListBox control showed the items sorted correctly using the SortDescriptions, but the AutoCompleteBox list didn't have the items sorted. It had them in the order they were added to the collection.
Thoughts? Suggestions? Has anyone done this?
I have no idea how #user1089031 done this, but here is working sample for anyone who could be interested in (updated to #adabyron's comment!):
ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
namespace WpfApplication12
{
public class Item
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
public class ViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate {};
private readonly ObservableCollection<Item> source;
private readonly ICollectionView items;
private string searchText;
public ViewModel()
{
source = new ObservableCollection<Item>
{
new Item {Name = "111111111 Test abb - (1)"},
new Item {Name = "22222 Test - (2)"},
new Item {Name = "333 Test - (3)"},
new Item {Name = "44444 Test abc - (4)"},
new Item {Name = "555555 Test cde - (5)"},
new Item {Name = "66 Test - bbcd (6)"},
new Item {Name = "7 Test - cd (7)"},
new Item {Name = "Test - ab (8)"},
};
items = new ListCollectionView(source);
}
public ICollectionView Items
{
get { return items; }
}
public IEnumerable<Item> ItemsSorted
{
get
{
return string.IsNullOrEmpty(SearchText)
? source
: (IEnumerable<Item>)source
.OrderBy(item => item.Name.IndexOf(SearchText,
StringComparison.InvariantCultureIgnoreCase));
}
}
public Item Selected { get; set; }
public string SearchText
{
get { return searchText; }
set
{
searchText = value;
PropertyChanged(this,
new PropertyChangedEventArgs("SearchText"));
PropertyChanged(this,
new PropertyChangedEventArgs("ItemsSorted"));
}
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
xmlns:wpfApplication2="clr-namespace:WpfApplication12"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="200" Width="500"
DataContext="{DynamicResource viewModel}">
<Window.Resources>
<wpfApplication2:ViewModel x:Key="viewModel" />
<DataTemplate DataType="{x:Type wpfApplication2:Item}">
<TextBlock Text="{Binding Name}" FontFamily="Courier New" />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<controls:AutoCompleteBox
ItemsSource="{Binding ItemsSorted}"
FilterMode="ContainsOrdinal"
SelectedItem="{Binding Selected, Mode=TwoWay}"
MinimumPrefixLength="0"
VerticalAlignment="Top" Margin="5">
<i:Interaction.Behaviors>
<wpfApplication2:SearchTextBindBehavior
BoundSearchText="{Binding SearchText,
Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
</controls:AutoCompleteBox>
<ListBox Grid.Column="1"
ItemsSource="{Binding Items}" Margin="5" />
</Grid>
</Window>
As you could notice I've add one custom behavior to AutoCompleteBox control:
<i:Interaction.Behaviors>
<wpfApplication2:SearchTextBindBehavior
BoundSearchText="{Binding SearchText,
Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
This is because AutoCompleteBox's own SearchText property is read-only. So here is the code of this behavior:
SearchTextBindBehavior.cs (Updated)
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace WpfApplication12
{
public class SearchTextBindBehavior : Behavior<AutoCompleteBox>
{
public static readonly DependencyProperty BoundSearchTextProperty =
DependencyProperty.Register("BoundSearchText",
typeof(string), typeof(SearchTextBindBehavior));
public string BoundSearchText
{
get { return (string)GetValue(BoundSearchTextProperty); }
set { SetValue(BoundSearchTextProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += OnTextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= OnTextChanged;
}
private void OnTextChanged(object sender, RoutedEventArgs args)
{
if(AssociatedObject.Text.Length == 0)
{
BoundSearchText = string.Empty;
return;
}
if(AssociatedObject.SearchText ==
AssociatedObject.Text.Substring(0,
AssociatedObject.Text.Length - 1))
{
BoundSearchText = AssociatedObject.Text;
}
}
}
}
Note: To make it all work you will need to add reference to the System.Windows.Interactivity.dll from the Expression Blend 4 SDK. This is just where Behavior<T> and a few its friends live.
If you have Expression Blend already installed, you already have all the SDK and there is no need to download anything. Just in case - on my machine the assembly located here:
C:\Program Files\Microsoft SDKs\Expression\Blend.NETFramework\v4.0\Libraries\System.Windows.Interactivity.dll
And, finally, if you have some good reason to do NOT add reference to this popular official library, feel free to re-implemented this custom behavior in "the old way" via plain old attached properties.
Hope that helps.
This is what I ended up with, a slight adaptation of Sevenate's answer, so if you wanted to upvote, do that to his post.
I used a subclass (I had the AutoCompleteBox subclassed already for other reasons), which allows me to create a wrapper dependency property to get the readonly SearchText (=what the user entered via keyboard) to the ViewModel - instead of a blend behavior, which is a perfectly valid way, too.
The crux of the matter is that you should only apply the dynamic sorting upon changes of SearchText, not Text (=what is displayed in the AutoCompleteBox, will also change if a suggestion is selected in the dropdown). Sevenate's way to raise the PropertyChanged event of the readonly ItemsSource (ItemsSorted) is a nice and clean way to apply the sorting.
ViewModel:
public class Item
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
public class AutoCompleteBoxDynamicSortingVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly ObservableCollection<Item> source;
public AutoCompleteBoxDynamicSortingVM()
{
source = new ObservableCollection<Item>
{
new Item {Name = "111111111 Test abb - (1)"},
new Item {Name = "22222 Test - (2)"},
new Item {Name = "333 Test - (3)"},
new Item {Name = "44444 Test abc - (4)"},
new Item {Name = "555555 Test cde - (5)"},
new Item {Name = "66 Test - bbcd (6)"},
new Item {Name = "7 Test - cd (7)"},
new Item {Name = "Test - ab (8)"},
};
}
public IEnumerable<Item> ItemsSorted
{
get
{
return string.IsNullOrEmpty(Text) ? (IEnumerable<Item>)source :
source.OrderBy(item => item.Name.IndexOf(Text, StringComparison.OrdinalIgnoreCase));
}
}
public Item Selected { get; set; }
// Text that is shown in AutoCompleteBox
private string text;
public string Text
{
get { return text; }
set { text = value; OnPropertyChanged("Text"); }
}
// Text that was entered by user (cannot be changed from viewmodel)
private string searchText;
public string SearchText
{
get { return searchText; }
set
{
searchText = value;
OnPropertyChanged("SearchText");
OnPropertyChanged("ItemsSorted");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Subclass of AutoCompleteBox:
public class MyAutoCompleteBox : AutoCompleteBox
{
/// <summary>
/// Bindable property that encapsulates the readonly property SearchText.
/// When the viewmodel tries to set SearchText by way of EnteredText, it will fail without an exception.
/// </summary>
public string EnteredText
{
get { return (string)GetValue(EnteredTextProperty); }
set { SetValue(EnteredTextProperty, value); }
}
public static readonly DependencyProperty EnteredTextProperty = DependencyProperty.Register("EnteredText", typeof(string), typeof(MyAutoCompleteBox), new PropertyMetadata(null));
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
// synchronize SearchText and EnteredText (only one-way)
if (e.Property == AutoCompleteBox.SearchTextProperty && this.EnteredText != this.SearchText)
EnteredText = SearchText;
base.OnPropertyChanged(e);
}
}
Xaml:
<UserControl x:Class="WpfApplication1.Controls.AutoCompleteBoxDynamicSorting"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myctrls="clr-namespace:WpfApplication1.Controls"
xmlns:models="clr-namespace:WpfApplication1.ViewModels"
Height="350" Width="525"
DataContext="{DynamicResource viewModel}">
<UserControl.Resources>
<models:AutoCompleteBoxDynamicSortingVM x:Key="viewModel" />
<DataTemplate DataType="{x:Type models:Item}">
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<myctrls:MyAutoCompleteBox
ItemsSource="{Binding ItemsSorted}"
Text="{Binding Text, Mode=TwoWay}"
EnteredText="{Binding SearchText, Mode=OneWayToSource}"
FilterMode="ContainsOrdinal"
VerticalAlignment="Top" Margin="5" />
</Grid>
</UserControl>
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;
}
Is there any way, how to force ObservableCollection to fire CollectionChanged?
I have a ObservableCollection of objects ListBox item source, so every time I add/remove item to collection, ListBox changes accordingly, but when I change properties of some objects in collection, ListBox still renders the old values.
Even if I do modify some properties and then add/remove object to the collection, nothing happens, I still see old values.
Is there any other way around to do this? I found interface INotifyPropertyChanged, but I don't know how to use it.
I agree with Matt's comments above. Here's a small piece of code to show how to implement the INotifyPropertyChanged.
===========
Code-behind
===========
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
Nicknames names;
public Window1()
{
InitializeComponent();
this.addButton.Click += addButton_Click;
this.names = new Nicknames();
dockPanel.DataContext = this.names;
}
void addButton_Click(object sender, RoutedEventArgs e)
{
this.names.Add(new Nickname(myName.Text, myNick.Text));
}
}
public class Nicknames : System.Collections.ObjectModel.ObservableCollection<Nickname> { }
public class Nickname : System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
void Notify(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propName));
}
}
string name;
public string Name
{
get { return name; }
set
{
name = value;
Notify("Name");
}
}
string nick;
public string Nick
{
get { return nick; }
set
{
nick = value;
Notify("Nick");
}
}
public Nickname() : this("name", "nick") { }
public Nickname(string name, string nick)
{
this.name = name;
this.nick = nick;
}
public override string ToString()
{
return Name.ToString() + " " + Nick.ToString();
}
}
}
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<DockPanel x:Name="dockPanel">
<TextBlock DockPanel.Dock="Top">
<TextBlock VerticalAlignment="Center">Name: </TextBlock>
<TextBox Text="{Binding Path=Name}" Name="myName" />
<TextBlock VerticalAlignment="Center">Nick: </TextBlock>
<TextBox Text="{Binding Path=Nick}" Name="myNick" />
</TextBlock>
<Button DockPanel.Dock="Bottom" x:Name="addButton">Add</Button>
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
</DockPanel>
</Grid>
Modifying properties on the items in your collection won't fire NotifyCollectionChanged on the collection itself - it hasn't changed the collection, after all.
You're on the right track with INotifyPropertyChanged. You'll need to implement that interface on the class that your list contains. So if your collection is ObservableCollection<Foo>, make sure your Foo class implements INotifyPropertyChanged.