I am trying to change the selected item of a combo box based on a change in another combo box. The situation is complicated by the fact that both combo boxes appear in a list of item templates.
The XAML is as follows:
<ListBox ItemsSource="{Binding AncillaryExtendedPropertyViewModels}" ItemTemplateSelector="{StaticResource templateSelector}"/>
<DataTemplate x:Key="EnumDataTemplate"> <Grid Margin="4"
MinHeight="25"> <ComboBox SelectedItem="{Binding ExtendedPropertyEnum,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
ItemsSource="{Binding ExtendedPropertyEnumList}"
DisplayMemberPath="Value"/> </Grid> </DataTemplate>
The data context of the view containing the XAML is set to AncillaryBaseViewModel. The following is a cut down version of AncillaryBaseViewModel.
public class AncillaryBaseViewModel : ComplexOrderItemViewModel, IDataErrorInfo
{
private ObservableCollection<ExtendedPropertyViewModel> _ancillaryExtendedPropertyViewModels;
public ObservableCollection<ExtendedPropertyViewModel> AncillaryExtendedPropertyViewModels
{
get { return _ancillaryExtendedPropertyViewModels; }
set
{
_ancillaryExtendedPropertyViewModels = value;
OnPropertyChanged("AncillaryExtendedPropertyViewModels");
}
}
and the ExtendedPropertyViewModel class....
public class ExtendedPropertyViewModel : DataTemplateSelector
{
private ExtendedProperty _extendedProperty;
public DataTemplate DefaultnDataTemplate { get; set; }
public DataTemplate BooleanDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }
public ExtendedPropertyEnum ExtendedPropertyEnum
{
get
{ return ExtendedProperty.ExtendedPropertyEnum; }
set
{
if (ExtendedProperty.ExtendedPropertyEnum != value)
{
_extendedProperty.ExtendedPropertyEnum = value;
AncillaryBaseViewModel parent = RequestParent();
if (parent != null)
{
parent.AncillaryExtendedPropertyViewModels[7].ExtendedPropertyEnum =
ExtendedProperty.ExtendedPropertyEnum.DependentExtendedPropertyEnums[0];
}
parent.OrderItem.SetStockCode();
PropertyChanged += parent.OnExtendedPropertyChanged;
OnPropertyChanged();
}
}
}
and the ExtendedProperty class....
public class ExtendedProperty : ViewModelBase
{
private ExtendedPropertyEnum _extendedPropertyEnum;
public int ExtendedPropertyID { get; set; }
public int OrderItemTypeID { get; set; }
public string DisplayName {get; set;}
public ObservableCollection<ExtendedPropertyEnum> ExtendedPropertyEnumList { get; set; }
public string Value { get; set; }
public ExtendedPropertyEnum ExtendedPropertyEnum
{
get
{
return _extendedPropertyEnum;
}
set
{
_extendedPropertyEnum = value;
OnPropertyChanged("ExtendedPropertyEnum");
}
}
}
To summarise, when I change the value of combo box A through the UI, this calls the ExtendedPropertyEnum setter within ExtendedPropertyViewModel, which changes the ExtendedPropertyEnum bound to another combo box B, which is in the same list. I would expect this to update the value displayed in combo box B accordingly, which it does not.
As an aside, changing the value of combo box A does update a label that is not within a data template. The XAML for this label is....
<Label Content="{Binding StockCode}" MinWidth="100"/>
This is updated by the following code within AncillaryBaseViewModel....
public void OnExtendedPropertyChanged(object sender, EventArgs args)
{
OnPropertyChanged("StockCode");
}
I thought I could change this to the following to force the combo boxes in the list to update.
public void OnExtendedPropertyChanged(object sender, EventArgs args)
{
OnPropertyChanged("StockCode");
OnPropertyChanged("AncillaryExtendedPropertyViewModels");
}
However, this did not work either.
Any help greatly appreciated.
Roger.
if I understand the question correctly then you are expecting a changed value within an observablecollection to be reflected within the UI. This will not happen. observablecollections raise notifyproperty events when the collection itself changes, not when values within them change. You'll either need to raise a notifyproperty event on the value changing or remove the item from the list and add it back with a new value.
Related
I have datagrid with two columns: text and combobox. And combobox should have binding to observable collection.
This is pseudocode for datagrid items source:
public class ModeObjectState
{
public int ID { get; set; }
public int ObjectTypeID { get; set; }
public string State { get; set; }
}
public class ModeObject
{
public string Name { get; set; }
public int objID { get; set; }
public int Type { get; set; }
public int StateID { get; set; }
public bool Format { get; set; }
}
public class _dataContext
{
public ObservableCollection<ModeObjectState> ListObjectState { get; set; }
public ModeObject ModeObj { get; set; }
}
ObservableCollection<_dataContext> SourceObjList
objTable.ItemsSource = SourceObjList;
This is xaml code for datagrid:
<DataGrid x:Name="objTable" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn x:Name="ColumnName" Binding="{Binding Path=ModeObj.Name}" IsReadOnly="True" />
<DataGridComboBoxColumn x:Name="ColumnState" ItemsSource="{Binding ListObjectState}" DisplayMemberPath="State" SelectedValuePath="ID" SelectedValueBinding="{Binding Path=ModeObj.StateID}" />
</DataGrid.Columns>
</DataGrid>
But datagrid doesn't show any items in comboboxcolumn. Please, help me with binding the datagridcombobox to observable collection "ListObjectState" in "_dataContext" class.
Thanks!
Implement with INotifyPropertyChanged for the _dataContext
public class _dataContext : INotifyPropertyChanged
{
private ObservableCollection<ModeObjectState> _listObjectState;
public ObservableCollection<ModeObjectState> ListObjectState
{
get { return _listObjectState; }
set
{
_listObjectState = value;
OnPropertyChagned("ListObjectState");
}
}
public ModeObject ModeObj { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChagned(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Since the 1st set may done after the Binding, so it wont affect the UI..
Its difficult to figure out without looking at the whole code. You have binding issue, and it would be easier to find where the issue is using XAML debugging tools like Snoop or WPF Inspector. You just need to attach your running application to see the Datacontext.
You can easily find if the datacontext is valid or not.
WPF Inspector has a better User interface, but its prone to crash. Press Ctrl+Shift and hover mouse over your control to see it getting reflected in Snoop/WPF Inspected.
Also see your Output window for whats the binding error you are getting.
I have a ListBox within a DataTemplate of another ListBox simplified to the following XAML
<ListBox ItemsSource="{Binding MovieList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ListBox ItemsSource="{Binding Cast}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCastMember, Mode=TwoWay}"/>
<TextBlock Text="{Binding MovieName}"/>
<TextBlock Text=....../>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The Parent List is bound to an ObservableCollection of Movie with each Movie object in turn having an ObservableCollection of cast members bound to a list box. The ViewModel property and Class below
public const string MovieListPropertyName = "MovieList";
private ObservableCollection<Movie> _movieList;
public ObservableCollection<Movie> MovieList
{
get
{
return _movieList;
}
set
{
if (_movieList == value)
{
return;
}
RaisePropertyChanging(MovieListPropertyName);
_movieList = value;
RaisePropertyChanged(MovieListPropertyName);
}
}
public const string SelectedCastMemberPropertyName = "SelectedCastMember";
private MovieCastMember _selectedCastMember;
public MovieCastMember SelectedCastMember
{
get
{
return _selectedCastMember;
}
set
{
if (_selectedCastMember == value)
{
return;
}
RaisePropertyChanging(SelectedCastMemberPropertyName);
_selectedCastMember = value;
RaisePropertyChanged(SelectedCastMemberPropertyName);
}
}
With the Movie and MovieCastMember classes as follows
public class Movie
{
public int Id { get; set; }
public string Name { get; set; }
public int Year { get; set; }
public string Overview { get; set; }
public double VoteAverage { get; set; }
public ObservableCollection<MovieCastMember> Cast { get; set; }
public BitmapImage PosterImage { get; set; }
}
public class MovieCastMember
{
public int Id { get; set; }
public string Name { get; set; }
}
I want to select a cast member in any of the Movie Lists and bind the MovieCastMember object to a property in my ViewModel. My List boxes populates fine, I have tried various scenarios in XAML but the SelectedItem are not updating the property in the ViewModel. Any help would be appreciated.
That's because you're binding against Movie class in second listBox. There is no "SelectedCastMember". Move it to Movie class and it will work.
I have a wpf combobox bound to a property LogicalP of a class SInstance. The ItemSource for the combobox is a dictionary that contains items of type LogicalP.
If I set LogicalP in SInstance to an initial state, the combobox text field shows empty. If I select the pulldown all my dictionary values are there. When I change the selection LogicalP in SInstance gets updated correctly. If I change Sinstance in C# the appropriate combobox value doesn't reflect the updated LogicalP from the pulldown.
I've set the binding mode to twoway with no luck. Any thoughts?
My Xaml:
<UserControl.Resources>
<ObjectDataProvider x:Key="PList"
ObjectType="{x:Type src:MainWindow}"
MethodName="GetLogPList"/>
</UserControl.Resources>
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding Source={StaticResource PList}}"
DisplayMemberPath ="Value.Name"
SelectedValuePath="Value"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
My C#:
public Dictionary<string, LogicalPType> LogPList { get; private set; }
public Dictionary<string, LogicalPType> GetLogPList()
{
return LogPList;
}
public class LogicalPType
{
public string Name { get; set; }
public string C { get; set; }
public string M { get; set; }
}
public class SInstance : INotifyPropertyChanged
{
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
They are not looking at the same source.
You need to have SInstance supply both the LogPList and LogicalP.
_LogicalP is not connected to LogPList
If you want to different objects to compare to equal then you need to override Equals.
Here's my working solution. By moving the dictionary retrieval GetLogPList to the same class as that supplies the data (as suggested by Blam) I was able to get the binding to work both ways. I changed binding to a list rather than a dictionary to simplify the combobox
Here's the updated Xaml showing the new ItemsSource binding and removal of the SelectedValuePath:
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding GetLogPList}"
DisplayMemberPath ="Name"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
I then changed the dictionary LogPList to static so that it would be accessible to the class SInstance:
public static Dictionary<string, LogicalPType> LogPList { get; private set; }
Finally, I moved GetLogPList to the class SInstance as a property. Note again it's returning a list as opposed to a dictionary to make the Xaml a little simpler:
public class SInstance : INotifyPropertyChanged
{
public List<LogicalPType> GetLogPList
{
get { return MainWindow.LogPList.Values.ToList(); }
set { }
}
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
There are several articles written about similar tasks to this. However, none seem to be close enough to work for what I am doing. I have a custom control that has a ListBox in its template. I have reworked the template for the ListBox to my liking. When an item is selected, I want to change the color. Here's the part where my problem seems to diverge to most others: I don't know what color. It is whatever color is in the item being rendered. I bound the different colors in XAML, but it doesn't redraw when I set a new color. I have changed the default color in the items to make sure that the template was picking up the right values in the first place. That succeeded. Things I have tried: binding, having the items implement INotifyPropertyChanged, and EventTrigger with Storyboard (which never really built I assume because my value wasn't a static resource). I am missing something very basic here. I'm sure. Here are code excerpts to help:
XAML:
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="{Binding CurrentState.Border}" BorderThickness="1">
<TextBlock Text="{Binding DisplayObject}" Foreground="{Binding CurrentState.Foreground}" Background="{Binding CurrentState.Background}" MinHeight="12" MinWidth="50" Padding="2" ToolTip="{Binding ToolTip}"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
Helper classes:
public class MultiStateSelectionGridState
{
public string Background { get; set; }
public string Foreground { get; set; }
public string Border { get; set; }
public string Text { get; set; }
public MultiStateSelectionGridState()
{
Background = "White";
Foreground = "Black";
Border = "Black";
Text = String.Empty;
}
};
public interface IMultiStateSelectionGridItem : INotifyPropertyChanged
{
object DisplayObject { get; }
object ToolTip { get; }
object Value { get; }
MultiStateSelectionGridState CurrentState { get; set; }
void OnPropertyChanged(PropertyChangedEventArgs e);
};
I don't know how much of the item class I can post, so I will not do so initially. It looks like the following though:
class SomeItem : IMultiStateSelectionGridItem
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
public string SomeOtherString { get; set; }
public object DisplayObject
{
get { return SomeString + CurrentState.Text; }
}
public object ToolTip
{
get { return SomeOtherString; }
}
public object Value
{
get { return SomeInt; }
}
private MultiStateSelectionGridState m_currentState;
public MultiStateSelectionGridState CurrentState
{
get
{
return m_currentState;
}
set
{
m_currentState = value;
//Notice that this was just test code and I tried CurrentState, Background, and what
//you see there now.
OnPropertyChanged(new PropertyChangedEventArgs("CurrentState.Background"));
}
}
public SomeItem()
{
SomeInt = 0;
SomeString = String.Empty;
SomeOtherString = String.Empty;
CurrentState = new MultiStateSelectionGridState();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
};
Any help would be much appreciated.
Ok your implementing INPC in SomeItem and not MultiStateSelectionGridState so when you switch state you need to pretty much create a new CurrentState object than go something like CurrentState.Background = "Blue"; in code-behind
You also need to switch
OnPropertyChanged(new PropertyChangedEventArgs("CurrentState.Background"));
to
OnPropertyChanged(new PropertyChangedEventArgs("CurrentState"));
Now when you switch the CurrentState variable(when item is selected) it will propagate to the View and it's properties will be queried accordingly.
I tested this by doing something like:
Items = new ObservableCollection<SomeItem> {
new SomeItem {
CurrentState = new MultiStateSelectionGridState()
}
};
// Simulating a Selected State change
var tempTask = new Task
(
() => {
Thread.Sleep(5000);
Items[0].CurrentState = new MultiStateSelectionGridState {
Background = "Green",
Border = "Blue"
};
},
TaskCreationOptions.LongRunning
);
tempTask.Start();
and having ListBox have it's ItemsSource as Items from above.
You can find a working example of this Here
If you do not want to keep re-creating the CurrentState object for state changes, make
MultiStateSelectionGridState implement INPC itself.
Side-note
I do not know where or when you actually get to know what Colors to set for the control, so cant advise how to move that to xaml. But you should look to having these come from xaml
I copied the basic method of having checkbox in a treeview from the official Silverlight toolkit Checkboxes in a TreeView example.
When a user clicks on a parent TreeViewItem I want all of the child items to be checked, as in the above example. This works fine when the parent is collapsed, clicking the checkbox puts a tick in the parent and when you expand the node all children have a tick in the checkbox.
However it doesn't work if the parent is expanded. None of the children are updated to have a tick in the checkbox, although the underlying data list is updated.
My XAML is as follows:
<sdk:HierarchicalDataTemplate x:Key="NodeTemplate" ItemsSource="{Binding Path=Contracts}">
<StackPanel Orientation="Horizontal" ToolTipService.ToolTip="{Binding Path=Name}">
<CheckBox IsTabStop="False" IsThreeState="{Binding Path=HasContracts}" IsChecked="{Binding Path=Selected, Mode=TwoWay}" Click="CheckBox_Click" />
<TextBlock Text="{Binding Path=Name}" Tag="{Binding Path=ID}"/>
</StackPanel>
</sdk:HierarchicalDataTemplate>
<sdk:TreeView x:Name="tvClientContract" ItemsSource="{Binding Path=ClientContracts, Mode=TwoWay}" ItemTemplate="{StaticResource NodeTemplate}"/>
This is bound to a List<ClientContract> and uses the same code behind as in the linked example.
The ClientContract object is:
public int ID { get; set; }
public string Name { get; set; }
public List<ClientContract> Contracts { get; set; }
public bool? Selected { get; set; }
How can I force the child to repaint itself as the underlying List<ClientContract> object is updated?
If you want to use INotifyPropertyChange(what I did instead of using ObservableCollection) here is how you do it per example on the ID element:
public class myclass : INotifyPropertyChanged
{
private int id_Value;
public int ID
{
get { return id_Value; }
set
{
id_Value = value;
NotifyPropertyChanged("ID");
}
}
public string Name { get; set; }
public List<ClientContract> Contracts { get; set; }
public bool? Selected { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
I hope this helps if it was what you were trying to do.
Try using ObservableCollection<ClientContract> instead of a List<>. Usually you want to databind to this collection type instead when the data is dynamic so it can notify the UI of collection changes.