WPF: Use property field value in ComboBox instead of the object name - wpf

My combobox is bound with google search results.
<ComboBox
Style="{StaticResource ComboBoxStyle}"
IsEditable="True"
IsTextSearchEnabled="False"
ItemsSource="{Binding GoogleSuggest.SuggestedQueries}"
SelectedItem="{Binding GoogleSuggest.SelectedQuery}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source={Binding IconPath, Converter={StaticResource IconPathToImageSource} Width="32" Height="32" />
<StackPanel Grid.Column="1">
<TextBlock Text="{Binding Query}" Margin="0,8" FontSize="24" />
<TextBlock Text="{Binding URL}" Margin="0,8" FontSize="16" />
</StackPanel>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
My Model looks like this
public class Model_SuggestedQueries : ViewModelBase
{
private string _Query = string.Empty;
public string Query
{
get { return _Query; }
set
{
if (_Query != value)
{
_Query = value;
base.RaisePropertyChanged("Query");
}
}
}
private int _Index = 0;
public int Index
{
get { return _Index; }
set
{
if (_Index != value)
{
_Index = value;
base.RaisePropertyChanged("Index");
}
}
}
private string _URL = 0;
public string URL
{
get { return _URL; }
set
{
if (_URL != value)
{
_URL = value;
base.RaisePropertyChanged("URL");
}
}
}
private string _Icon = 0;
public string Icon
{
get { return _Icon; }
set
{
if (_Icon != value)
{
_Icon = value;
base.RaisePropertyChanged("Icon");
}
}
}
}
But when I make a selection, the .Text field looked like this.
How can I show the "Query" value instead of the object name?

Have you tried to add the DisplayMemberPath attribute to your ComboBox Control ?
<ComboBox
Style="{StaticResource ComboBoxStyle}"
IsEditable="True"
IsTextSearchEnabled="False"
ItemsSource="{Binding GoogleSuggest.SuggestedQueries}"
SelectedItem="{Binding GoogleSuggest.SelectedQuery}"
DisplayMemberPath="Query"
>
If it doesn't work you may try to override the ToString() method of your Model_SuggestedQueries class.

Add TextSearch.TextPath="Query" to your ComboBox markup.
See MSDN Textsearch.Textpath

I think there is an easier way to achieve your goal,try this:
Just override the ToString() function of your class"Model_SuggestedQueries"
:p

Related

How to update a listview from viewmodel binded model observable collection to itemsource

I have a list view, which further contains textbox in datatemplate having columns Name, Address, Country.
Now this listview's Itemsource is binded to observable collection of a model in my viewmodel class.
I am updating the ObjModel (of typeObservableCollection<Model>) by making name and address empty, on some condition in VM, and i can see the values of name, empty in my ObjModel object in viewmodel class.
But those changes are not reflected in UI (ListView), Am i missing something , How to update the listview.
My View is something like this:
<DataTemplate x:Key="EquipmentItemTemplate">
<Grid
x:Name="ListItem"
Height="40"
ZIndex="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="250" />
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=Name}" />
<TextBlock Grid.Column="1" Text="{Binding Path=Address}" />
<TextBlock Grid.Column="2" Text="{Binding Path=Country}" />
</Grid>
</DataTemplate>
<ListView x:Name="MaintenanceElements"
ItemContainerStyle="{StaticResource SequenceListItemStyle}"
ItemTemplate="{StaticResource EquipmentItemTemplate}"
ItemsSource="{Binding EquipmentMaintenanceEntityItemsCollection}"
SelectedItem="{Binding SelectedMaintenanceElement}">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn
Width="200"
local:GridViewSort.PropertyName="Name"
Header="Name" />
<GridViewColumn
Width="250"
local:GridViewSort.PropertyName="Address"
Header="Address" />
<GridViewColumn
Width="250"
local:GridViewSort.PropertyName="Country"
Header="Country" />
</GridView>
</ListView.View>
</ListView>
View Model contains:
public ObservableCollection<Model> ObjModel { get; set; }
Some where on some condition i do
ObjModel[0].Name= string.Empty;
It do not update in ListView because its itemsource is binded to Model object observable collection, how to update ListView from here?
My model is:
public class EquipmentMaintenanceModel : ChangeTracker, INotifyPropertyChanged
{
private string name;
private string address;
private string country;
public string Name
{
get { return this.name; }
set { this.name = value; }
}
public string Address
{
get { return this.address; }
set { this.address = value; }
}
public string Country
{
get { return this.country; }
set { this.country = value; }
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In your model, you need to fire PropertyChanged when you set the value of a property. eg:
public string Name
{
get { return this.name; }
set
{
if(this.name != value)
{
this.name = value;
OnPropertyChanged(Name);
}
}
}

Event Aggregator in Prism usage giving exception

I have to communicate between 2 view models so I am using Event aggregator but I observed the method is calling 2 times when a property is updated and for the first it is working as expected but second time all the elements are null and throwing null reference exception.
Why it is happening I am not triggering it second time.
MainViewModel
private XMLNode NodeSelected;
public XMLNode NodeSelected
{
get { return NodeSelected; }
set
{
nodeSelected = value;
iEventAggregator.GetEvent<AddNewObjectEvent>().Publish(this.NodeSelected);
}
}
UsercontrolViewModel
public UserControlViewModel(IEventAggregator iEventAggregator)
{
this.iEventAggregator = iEventAggregator;
this.iEventAggregator.GetEvent<AddNewGuiSyncObjectEvent>()
.Subscribe(AddXMLNode);
}
AddXMLNODE method related code
private void AddXMLNODE (XMLNode SelectedNode)
{
if (this.CriteriaItem == null) return;
SyncObject currentObj = new SyncObject(SelectedNode);
if (!IsSyncItemNameUnique(currentObj))
{
currentObj.Name = GetUniqueName(currentObj);
}
this.CriteriaItem.SyncObjects.Add(currentObj);
this.SelectedSyncObject = this.CriteriaItem .SyncObjects.LastOrDefault();
}
Here first time "CriteriaItem" is coming correct and newnode is added to collection but again same method is hitting and CriteriaItem is NULL If I remove that null check we are getting exception.
What is the mistake here I am not getting.
The complete code
UserControl ViewModel
namespace Hexagon.SmartUITest.GUISynchronization.ViewModel
{
public class IndividualSyncViewModel : ViewModelBase
{
#region properties
private GUISyncObject selectedSyncObject;
public GUISyncObject SelectedSyncObject
{
get { return selectedSyncObject; }
set
{
selectedSyncObject = value;
this.OnPropertyChanged(() => this.SelectedSyncObject);
iEventAggregator.GetEvent<SelectedSyncObjectEvent>().Publish(this.SelectedSyncObject);
}
}
private SyncCriteriaItem _CriteriaItem;
public SyncCriteriaItem CriteriaItem
{
get { return CriteriaItem; }
set
{
CriteriaItem = value;
OnPropertyChanged(() => this.CriteriaItem);
}
}
private IEventAggregator iEventAggregator;
#endregion
#region constructor
public IndividualSyncViewModel(IEventAggregator iEventAggregator)
{
this.iEventAggregator = iEventAggregator;
this.iEventAggregator.GetEvent<AddNewGuiSyncObjectEvent>()
.Subscribe(AddGUISyncItem);
}
#endregion
private ICommand deleteSyncObjectClick;
public ICommand DeleteSyncObjectClick
{
get
{
if (deleteSyncObjectClick == null) deleteSyncObjectClick = new RelayCommand(DeleteSync);
return deleteSyncObjectClick;
}
set
{
deleteSyncObjectClick = value;
}
}
private void DeleteSync()
{
this.CriteriaItem.SyncObjects.Remove(selectedSyncObject);
selectedSyncObject = this.CriteriaItem.SyncObjects.LastOrDefault();
}
private bool IsItemNameUnique(GUISyncObject SyncObject)
{
if (this.CriteriaItem != null && this.CriteriaItem.SyncObjects.Count == 0)
return true;
foreach (GUISyncObject item in this.CriteriaItem.SyncObjects.ToList())
{
if (item.Name.Equals(SyncObject.Name) && !item.CommandNodeName.Equals(SyncObject.CommandNodeName))
return false;
}
return true;
}
private string GetUniqueName(GUISyncObject syncItem)
{
List<string> itemsNames = this.CriteriaItem.SyncObjects.Select(item => item.Name).ToList();
return CommonNamingRules.GetUniqueName(itemsNames, syncItem.Name);
}
private void AddGUISyncItem(ControlNode SelectedNode)
{
if (this.CriteriaItem == null) return;
GUISyncObject currentObj = new GUISyncObject(SelectedNode);
if (!IsSyncItemNameUnique(currentObj))
{
currentObj.Name = GetUniqueName(currentObj);
}
this.CriteriaItem.SyncObjects.Add(currentObj);
this.SelectedSyncObject = this.CriteriaItem.SyncObjects.LastOrDefault();
}
}
}
UserControl xaml.cs
public partial class IndividualSync : UserControl
{
private IndividualSyncViewModel _vm;
public IndividualSync()
{
InitializeComponent();
_vm = new IndividualSyncViewModel(Event.EventInstance.EventAggregator);
rootGrid.DataContext = _vm;
}
#region properties
public SyncCriteriaItem SyncCriteriaItem
{
get { return (SyncCriteriaItem)GetValue(SyncCriteriaItemProperty); }
set { SetValue(SyncCriteriaItemProperty, value); }
}
public static readonly DependencyProperty SyncCriteriaItemProperty = DependencyProperty.Register("SyncCriteriaItem", typeof(SyncCriteriaItem), typeof(IndividualSync), new PropertyMetadata(null, OnCriteriaItemSet));
public GUISyncObject SelectedSYNC
{
get { return (GUISyncObject)GetValue(SelectedSYNCDependencyProperty); }
set { SetValue(SelectedSYNCDependencyProperty, value); }
}
public static readonly DependencyProperty SelectedSYNCDependencyProperty = DependencyProperty.Register("SelectedSYNC", typeof(GUISyncObject), typeof(IndividualSync), new PropertyMetadata(null, OnGUISyncItemSelect));
private static void OnCriteriaItemSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((IndividualSync)d)._vm.CriteriaItem = e.NewValue as SyncCriteriaItem;
}
private static void OnSyncItemSelect(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((IndividualSync)d)._vm.SelectedSyncObject = e.NewValue as GUISyncObject;
}
#endregion
}
Iam creating two dependency properties on the usercontrol which will be set from parent usercontrol.
Parent usercontrol Xaml
<UserControl.Resources>
<Converters:IndexToTabNameConverter x:Key="TabIndexConverter"/>
<DataTemplate x:Key="SyncTabItemTemplate" >
<StackPanel VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Text="{Binding Converter={StaticResource TabIndexConverter},
RelativeSource={RelativeSource AncestorType={x:Type telerik:RadTabItem}}}"/>
<Button Margin="10,0,0,0"
Style="{StaticResource CloseButton}"
ToolTipService.ToolTip="Save and Close">
<Button.Content>
<Path Data="M0,0 L6,6 M6, 0 L0,6"
SnapsToDevicePixels="True"
Stroke="Black"
StrokeThickness="1" />
</Button.Content>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<uxt:EventToCommand CommandParameter="{Binding}" Command="{Binding Path=DataContext.RemoveSyncCriteriaCommand,ElementName=rootGrid}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="EmptyTemplate">
<TextBlock FontWeight="Bold" FontFamily="Comic Sans" FontStyle="Italic" Text="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="HeaderTemplate">
<Label FontWeight="Bold" HorizontalContentAlignment="Center" Content="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="SyncCriteriaTemplate">
<local:IndividualSync SyncCriteriaItem="{Binding}" SelectedGUISYNC="{Binding Path=DataContext.SelectedGUISYNCOBJECT}"/>
</DataTemplate>
<DataTemplate x:Key="newTabButtonHeaderTemplate">
<telerik:RadButton Style="{DynamicResource IconButtonStyle}" Width="20" Height="20" Command="{Binding Path=DataContext.NewCommand,ElementName=rootGrid}" ToolTip="Click to add new item">
<uxt:UxtXamlImage Template="{DynamicResource Com_Add}" />
</telerik:RadButton>
</DataTemplate>
<DataTemplate x:Key="itemContentTemplate">
<Grid/>
</DataTemplate>
<Selectors:TabItemTemplateSelector x:Key="headerTemplateSelector"
NewButtonTemplate="{StaticResource newTabButtonHeaderTemplate}"
ItemTemplate="{StaticResource SyncTabItemTemplate}"/>
<Selectors:TabItemTemplateSelector x:Key="contentTemplateSelector"
NewButtonTemplate="{StaticResource itemContentTemplate}"
ItemTemplate="{StaticResource SyncCriteriaTemplate}"/>
<Selectors:SyncTemplateSelector x:Key="syncTemplates">
<Selectors:SyncTemplateSelector.ProcessSyncTemplate>
<DataTemplate>
<telerik:Label Content="{Binding ProcessSync,StringFormat={}{0}}" BorderThickness="1" HorizontalContentAlignment="Left" FontStyle="Oblique" Width="{Binding Width,RelativeSource={RelativeSource AncestorType=telerik:RadListBox}}"/>
</DataTemplate>
</Selectors:SyncTemplateSelector.ProcessSyncTemplate>
<Selectors:SyncTemplateSelector.GUISyncTemplate>
<DataTemplate>
<Expander Header="GUISYNC" HeaderTemplate="{StaticResource HeaderTemplate}" Width="{Binding Width,RelativeSource={RelativeSource AncestorType=telerik:RadListBox}}" IsExpanded="{Binding Mode=TwoWay, Path=IsSelected, RelativeSource={RelativeSource AncestorType=telerik:RadListBoxItem, Mode=FindAncestor}}">
<telerik:RadTabControl ItemsSource="{Binding SyncCriteriaItem}" OverflowMode="Scroll" DropDownDisplayMode="Visible" SelectedItem="{Binding SelectedSyncCriteria, Mode=TwoWay}" SelectedIndex="0"
ItemTemplateSelector="{StaticResource headerTemplateSelector}"
ContentTemplateSelector="{StaticResource contentTemplateSelector}">
</telerik:RadTabControl>
</Expander>
</DataTemplate>
</Selectors:SyncTemplateSelector.GUISyncTemplate>
</Selectors:SyncTemplateSelector>
</UserControl.Resources>
<Grid Name="rootGrid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<telerik:RadListBox Margin="20,5,20,0" Grid.Row="0" Grid.Column="0" Name="synclist" ItemsSource="{Binding CurrentSyncObjectCollection.SyncObjects}" ItemTemplateSelector="{StaticResource syncTemplates}" SelectedItem="{Binding SelectedSyncObject,Mode=TwoWay}" >
</telerik:RadListBox>
<telerik:RadComboBox Grid.Row="1" Grid.Column="0" Name="SyncOption" EmptyText="Select a sync type to be added " Margin="0,8,0,5" EmptySelectionBoxTemplate="{StaticResource EmptyTemplate}" ItemsSource="{Binding SyncTypes}" Width="{Binding ActualWidth, ElementName=synclist}" HorizontalContentAlignment="Center" Height="25" SelectedIndex="{Binding SelectedSyncIndex,Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<uxt:EventToCommand Command="{Binding AddSelectedSyncClick}" CommandParameter="{Binding Path=SelectedValue,ElementName=SyncOption}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</telerik:RadComboBox>
<telerik:RadButton Style="{DynamicResource IconButtonStyle}" Grid.Column="1" Grid.Row="0" Width="20" Height="20" VerticalAlignment="Top" IsEnabled="{Binding Path=SelectedSyncObject,Converter={uxt:ObjectToBoolConverter}}" ToolTip="Click to delete selected Sync"
Command="{Binding DeleteSyncObjectClick}">
<uxt:UxtXamlImage Template="{DynamicResource Com_Delete}" />
</telerik:RadButton>
</Grid>
I hope this will help. I have debugged the code and I observed even two Items are binded for CriteriaItems Breakpoint hitting for dependency property criteria item 3 times and one time null is coming.
Even when I change Tab selection Dependency Property setting code is hitting 2 times instead of one time.first time null is setting for CriteriaItem and second time respective item is coming.

WPF AutocompeteBox in datagrid Cell does not work properly

I am testing the WPF AutoCompleteBox control in datagrid cell.
I met two problems:
1) when i navigate to the autocomplete cell , it does not automatically switch to edit mode,
2) When I switch into edit mode and I type something, the list of suggesstions doesn’t appears and I after closing the window, i have a debug error that says :
System.Windows.Data Error: 40 : BindingExpression path error: 'Names' property not found on 'object' ''Person' (HashCode=40808136)'. BindingExpression:Path=Names; DataItem='Person' (HashCode=40808136); target element is 'AutoCompleteBox' (Name='acb2'); target property is 'ItemsSource' (type 'IEnumerable')
Here The code
namespace WpfPlayingWithDatagrid
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyViewModel mv = new MyViewModel();
this.DataContext = mv;
}
}
}
public class MyViewModel : ObservableObject
{
ObservableCollection<Person> _names = null;
RelayCommand _loadClients;
RelayCommand _showSelectedPerson;
Person _selectedPerson;
public Person SelectedPerson
{
get { return _selectedPerson; }
set { _selectedPerson = value; }
}
public ObservableCollection<Person> Names
{
get { return _names; }
set { _names = value;
RaisePropertyChanged("Names");
}
}
public RelayCommand LoadClientCommand
{
get
{
if (_loadClients == null)
_loadClients = new RelayCommand(LoadCommandExecute);
return _loadClients;
}
}
private void LoadCommandExecute()
{
LoadClients();
}
public void LoadClients()
{
List<Person> ll = new List<Person>(5);
ll.Add(new Person(1,"ETS CUSTOMER1","Addresse1"));
ll.Add(new Person(2,"COMPX CUSTOMER2","Addresse 2"));
ll.Add(new Person(3,"ENTREPRISE3","Adresse3"));
ll.Add(new Person(4,"SOCIETE X4HERTZ","Addresse4"));
ll.Add(new Person(5,"CARCOMP","Addresse5"));
Names = new ObservableCollection<Person>(ll);
}
public RelayCommand ShowSelectedPersonCommand
{
get
{
if (_showSelectedPerson == null)
_showSelectedPerson = new RelayCommand(ShowSelectedPersonCommandExecute);
return _showSelectedPerson;
}
}
private void ShowSelectedPersonCommandExecute()
{
if (SelectedPerson != null)
MessageBox.Show(SelectedPerson.Nom);
else
MessageBox.Show("No selection.");
}
}}
and The XAML is as follows :
<Window x:Class="WpfPlayingWithDatagrid.MainWindow"
x:Name="wnd"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:gs="http://www.galasoft.ch/mvvmlight"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
xmlns:local="clr-namespace:WpfPlayingWithDatagrid"
Title="MainWindow" >
<Window.Resources>
<local:MyViewModel x:Key="MyViewModel"/>
<Style x:Key="acbStyle" TargetType="controls:AutoCompleteBox">
<Setter Property="FilterMode" Value="Contains"/>
<Setter Property="IsTextCompletionEnabled" Value="True"/>
</Style>
<DataTemplate x:Key="AutoCompleteBoxItemTemplate">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Code}" Width="20" />
<Label Content="{Binding Nom}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Column="1"
Content="Load Customers"
Command="{Binding LoadClientCommand}" Margin="10"/>
<DataGrid Grid.Row="1"
Grid.ColumnSpan="3"
AutoGenerateColumns="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
RowHeight="30"
Grid.Column="0"
SelectionUnit="Cell"
ItemsSource="{Binding Names,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.RowSpan="2"
>
<DataGrid.Columns>
<DataGridTextColumn
Binding="{Binding Code, Mode=TwoWay, StringFormat=\{0:#\}}" Header="Code" />
<DataGridTemplateColumn Header="Name" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Nom}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<controls:AutoCompleteBox
x:Name="acb2"
Text="{Binding Nom}"
ItemsSource="{Binding Names}"
ValueMemberBinding="{Binding Nom}"
Style="{StaticResource acbStyle}"
ItemTemplate="{StaticResource AutoCompleteBoxItemTemplate}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Adresse, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Adresse" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
and Person class :
namespace WpfPlayingWithDatagrid
{
public class Person
{
int code;
public int Code
{
get { return code; }
set { code = value; }
}
string nom;
public string Nom
{
get { return nom; }
set { nom = value; }
}
string adresse;
public string Adresse
{
get { return adresse; }
set { adresse = value; }
}
public Person(int c, string n, string a)
{
Code = c;
Nom = n;
Adresse = a;
}
}
}
Thank you in advance.
Due to the way DataGridColumns are implemented, binding to parent viewmodels are always problematic.
The reason you are getting the binding error is because the row is bound to Person, and Person does not have the Names property.
The names property occur on MyViewModel and can be accessed like this
<controls:AutoCompleteBox
x:Name="acb2"
Text="{Binding Nom}"
ItemsSource="{Binding Names,Source={StaticResource MyViewModel}}"
ValueMemberBinding="{Binding Nom}"
Style="{StaticResource acbStyle}"
ItemTemplate="{StaticResource AutoCompleteBoxItemTemplate}"
/>
Updated
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyViewModel mv = (MyViewModel) FindResource("MyViewModel");
this.DataContext = mv;
}
}

WPF ListBox selected item, stays selected while loosing focus

I have a parent ListBox which is made of two listboxes, and a child listbox, so 3 listboxes in total. The Item source for the first two are just two separate collections, these collections contain inner collection which then become an item source for the third listbox. If I select second item then the child listbox is populated again, if I go to the second parent listbox and chose something then the child listbox is populated. The problem is that if at this point I go back to the first listbox, and try to select an item that was previously selected, nothing happens, while im expecting it to populate the child listbox again.
Hopefully this makes sense, here is the XAML:
<Window x:Class="ExampleForStackOverFlow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="350" Height="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="Parents" FontSize="20" FontWeight="Bold" Background="LightBlue"/>
<TextBlock Text="First Listbox" FontSize="16" FontWeight="Bold" Background="LightBlue"/>
<ListBox ItemsSource="{Binding Parents1}" SelectedItem="{Binding SelectedParent}" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Text="Second Listbox" FontSize="16" FontWeight="Bold" Background="LightBlue"/>
<ListBox ItemsSource="{Binding Parents2}" SelectedItem="{Binding SelectedParent2}" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Text="Children" FontSize="16" FontWeight="Bold" Background="LightBlue"/>
<ListBox ItemsSource="{Binding Children}" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
This is the ViewModel
using System.Collections.ObjectModel;
namespace ExampleForStackOverFlow
{
public class MainWindowViewModel : NotifyPropertyChanged
{
public MainWindowViewModel()
{
Parents1 = new ObservableCollection<Parent>();
Parents2 = new ObservableCollection<Parent>();
foreach (var parent in ParentCollection.GetParents())
{
if (parent.Name == "Parent 1" || parent.Name == "Parent 2")
{
Parents1.Add(parent);
}
else
{
Parents2.Add(parent);
}
}
Children = new ObservableCollection<Child>();
}
public ObservableCollection<Parent> Parents1 { get; set; }
public ObservableCollection<Parent> Parents2 { get; set; }
private ObservableCollection<Child> children;
public ObservableCollection<Child> Children
{
get { return children; }
set
{
children = value;
OnPropertyChanged("Children");
}
}
private Parent selectedParent;
public Parent SelectedParent
{
get { return selectedParent; }
set
{
selectedParent = value;
Children = SelectedParent.Children;
OnPropertyChanged("SelectedParent");
}
}
private Parent selectedParent2;
public Parent SelectedParent2
{
get { return selectedParent2; }
set
{
selectedParent2 = value;
Children = SelectedParent2.Children;
OnPropertyChanged("SelectedParent2");
}
}
}
public class Parent : NotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
private ObservableCollection<Child> childres;
public ObservableCollection<Child> Children
{
get { return childres; }
set
{
childres = value;
OnPropertyChanged("Children");
}
}
}
public class Child : NotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
}
}
The value for selected did not change so it is not going to fire the set
If you set it to null then a re-select will be a new value
private Parent selectedParent;
public Parent SelectedParent
{
get { return selectedParent; }
set
{
selectedParent2 = null;
selectedParent = value;
Children = SelectedParent.Children;
OnPropertyChanged("SelectedParent");
OnPropertyChanged("SelectedParent2"); // you may not need this
}
}
private Parent selectedParent2;
public Parent SelectedParent2
{
get { return selectedParent2; }
set
{
selectedParent = null;
selectedParent2 = value;
Children = SelectedParent2.Children;
OnPropertyChanged("SelectedParent"); // you may not need this
OnPropertyChanged("SelectedParent2");
}
}

WPF Control losing focus when clicking on a tab

On a tabcontrol I have several tabpages, on one of the tabs there is a textbox in the content.
This textbox is content bound with a simple Path=PropertyName and UpdateSourceTrigger=LostFocus. The reason I am using LostFocus is I trap the Lost focus event of the Textbox and possibly reformat the text. This is a "time" textbox and if they enter "0900", I want to reformat to "09:00". This part works great when I press the tab key to move to the next control, but if I type "0900" then press one of the other tabs, I hit the lost focus and re-format the value in the textbox, BUT the bind never gets called to update my object. When I come back to the tab, the value is blanked out (or reset to the original value on the object)
Any ideas why textbox does not trigger the Binding update when changing tab page?
Note: this also happens with a regular textbox that does wire to the lost focus event. It seems to have something to do with click on the tab.
[[Added Code ]]
More notes:
1. I am dynamically creating the tabs and controls on the tab (not sure if that has something to do with it or not)
2. I am using the Prism libraries
MainWindow Xaml
<Window.Resources>
<DataTemplate DataType="{x:Type ctrls:myTextBoxDef}">
<Grid Width="300">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding LabelText}" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding DocValue,
Mode=TwoWay,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=LostFocus}"
/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsTabStop="False"
ItemsSource="{Binding Tabs, Mode=OneWay}"
SelectedItem="{Binding SelectedTab,
Mode=TwoWay,
NotifyOnSourceUpdated=True}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Margin="18,14,22,0"
Text="{Binding HeaderText}" />
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Content -->
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<AdornerDecorator Grid.Column="0">
<ItemsControl Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsTabStop="False"
ItemsSource="{Binding Controls,
Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Grid.Column="0"
Margin="10,5,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</AdornerDecorator>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Main Window Code Behind
public partial class MainWindow : Window
{
private DataContextObject obj = new DataContextObject();
public MainWindow()
{
InitializeComponent();
myTextBoxDef txt1 = new myTextBoxDef(obj, "Textbox 1", "TAB1TextBox1");
myTextBoxDef txt1b = new myTextBoxDef(obj, "Textbox 1 value", "TAB1TextBox1");
myTextBoxDef txt2 = new myTextBoxDef(obj, "Textbox 2", "TAB1TextBox2");
myTextBoxDef txt2b = new myTextBoxDef(obj, "Textbox 2 value", "TAB1TextBox2");
obj.Tabs.Add(new myTabDef("Tab 1", new ObservableCollection<myTextBoxDef>() { txt1, txt2 }));
obj.Tabs.Add(new myTabDef("Tab 2", new ObservableCollection<myTextBoxDef>() { txt1b, txt2b }));
obj.SelectedTab = obj.Tabs[0];
this.DataContext = obj;
}
}
Supporting objects
public class DataContextObject : NotificationObject
{
List<myTabDef> _tabs = new List<myTabDef>();
public List<myTabDef> Tabs
{
get
{
return _tabs;
}
}
private myTabDef _item;
public myTabDef SelectedTab
{
get
{ return _item; }
set
{
_item = value;
this.RaisePropertyChanged("SelectedItem");
}
}
private string _txt1 = "";
public string TAB1TextBox1
{
get { return _txt1; }
set
{
_txt1 = value;
this.RaisePropertyChanged("TAB1TextBox1");
}
}
private string _txt2 = "";
public string TAB1TextBox2
{
get { return _txt2; }
set
{
_txt2 = value;
this.RaisePropertyChanged("TAB1TextBox2");
}
}
private string _txt3 = "";
public string TAB2TextBox1
{
get { return _txt3; }
set
{
_txt3 = value;
this.RaisePropertyChanged("TAB2TextBox1");
}
}
}
public class myTabDef
{
public myTabDef(string tabText, ObservableCollection<myTextBoxDef> controls)
{
HeaderText = tabText;
_left = controls;
}
public string HeaderText { get; set; }
private ObservableCollection<myTextBoxDef> _left = new ObservableCollection<myTextBoxDef>();
public ObservableCollection<myTextBoxDef> Controls
{
get
{
return _left;
}
}
}
public class myTextBoxDef : NotificationObject
{
public myTextBoxDef(NotificationObject bound, string label, string bindingPath)
{
LabelText = label;
Path = bindingPath;
BoundObject = bound;
BoundObject.PropertyChanged += BoundObject_PropertyChanged;
}
public string LabelText
{
get;
set;
}
public NotificationObject BoundObject
{
get;
set;
}
public string DocValue
{
get
{
return PropInfo.GetValue(BoundObject, null) as string;
}
set
{
PropInfo.SetValue(BoundObject, value, null);
}
}
protected virtual void BoundObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals(Path))
{
this.RaisePropertyChanged("DocValue");
}
}
public string Path
{
get;
set;
}
private PropertyInfo pi = null;
protected PropertyInfo PropInfo
{
get
{
if (pi == null && BoundObject != null && !string.IsNullOrEmpty(Path))
{
PropertyInfo[] properties = BoundObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
pi = properties.Where((prop) => string.Compare(prop.Name, Path, true) == 0).FirstOrDefault();
}
return pi;
}
}
}
We have found a solution. I came cross this set of postings
https://groups.google.com/forum/#!topic/wpf-disciples/HKUU61A5l74
They talk about a control called TabControlEx. Towards the bottom (5th from the bottom) you will see a posting by Sacha Barber that has a zip file with an example.
It solved all our problems we were having.
here is also another link where the code for the Class is posted
http://updatecontrols.codeplex.com/discussions/214434

Resources