ItemsControl with databound ItemsSource and two-way binding of elements (replacable items) - wpf

I have an ObservableCollection of objects (e.g. Persons with First/Last Name) which I would like to display in an ItemsControl. Each Item is displayed in a custom "editor" control, which allows editing of the object's properties.
This part is working fine and fairly standard.
<ItemsControl ItemsSource="{Binding Persons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<custom:PersonEditor Person="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
However, the custom editor control also has the ability to replace the entire object is has received (rather than just editing a person's name, replace it with a new person object).
What I am looking for is a way to push this change back into the ObservableCollection. As it is now, changing the Person object within the editor does not replace the item in the list, which would be the desired outcome.
Any help would be appreciated.

If you don't have access to the custom control itself what you could try would be using a setter on a property to clear the ObservableCollection and re-add the items instead of outright replacing it.
For example:
private ObservableCollection<MyModel> _dataSource;
public ObservableCollection<MyModel> DataSource
{
get
{
return _dataSource;
}
set
{
_dataSource.Clear();
foreach(var item in value)
{
_dataSource.Add(item);
}
}
}
This would prevent the item itself from getting replaced which causes issues with ItemSources since they apparently only bind to the items property once.

Related

Why do I need to set IsSynchronizedWithCurrentItem for certain ComboBox ItemsSource binding sources?

I have a WPF control inheriting from ComboBox whose ItemsSource is bound to a list of elements, and whose SelectedItem is bound to another field. I have two references to the same list in my application: one resides in my MainWindow class, and one resides in my App class.
The list of elements used as the ItemsSource is assigned to the ComboBox at the time it is created. Thus, I expect to get an ItemsSourceChanged event.
The strange thing is, if the ItemsSource is bound to the MainWindow's list, then the ComboBox becomes populated with the correct SelectedItem drawn from the bound field. However, if I bind the ItemsSource to the App's copy of the list, then the SelectedItem becomes overwritten with null due to the ItemsSourceChanged event.
Here is the XAML for the ComboBox when it's binding to the App copy of the list:
<local:TagSelectionComboBox FilteredTagsList="{Binding Path=TagsList, Source={x:Static Application.Current}}" SelectedItem="{Binding TagValue}"></local:TagSelectionComboBox>
Here is the XAML for the ComboBox when it's binding to the MainWindow copy of the list:
<local:TagSelectionComboBox FilteredTagsList="{Binding TagsList, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding TagValue}"></local:TagSelectionComboBox>
Here is the MainWindow's property used for binding:
public ObservableCollection<Tag> TagsList
{
get { return proj.Sim.Tags; }
}
And here is the App's property used for binding:
public ObservableCollection<Tag> TagsList
{
get { return proj.Sim.Tags; }
}
So these two properties on App and MainWindow are returning the same list. Stepping through in the debugger confirms: proj.Sim.Tags contains the same list in both cases.
Why does changing from one binding source to another alter the binding behavior? Is something else going on?
I've found that if I explicitly set IsSynchronizedWithCurrentItem="True", then the behavior is the same in both cases. So it's almost like IsSynchronizedWithCurrentItem="True" is the default behavior when I use the MainWindow (RelativeSource) binding but IsSynchronizedWithCurrentItem="False" is the default behavior when I use the App (x:Static) binding.
Background: the ComboBox-derived control in this question is basically the same as YantingChen's answer for this question:
Dynamic filter of WPF combobox based on text input

WPF binding with master-view layout

I have a question regarding WPF binding using master-detail layout.
The master view contains a DevExpress GridControl with a TreeListView.
The detail view content is updated when the TreeListView selected item is changed (trough the binding).
The detail view contains just a ContentControl, with it's Content property bound to the current selected item of the TreeListView.
In order to display the detail view in a way that correspond to the type of the selected item, several DataTemplates are registered to the current application resources.
In my case, the DataTemplates are just one user control.
When the selection is changed in the master view TreeListView, the corresponding DataTemplate is correcly diplayed.
One of my detail view contains a DevExpress ListBoxEdit. The SelectedIndex is bound with UpdateSourceTrigger=PropertyChanged.
My problem is that the setter of the bound property is called with the value -1 when another item is selected in the master view.
It seems that the -1 default value is received when the control is unloaded or disposed or unbound. This screw up my view model.
Is there a proper way to avoid to be notified in the ViewModel when the control is unloaded?
[update]
The ContentControl that contains the detail view is defined like this:
<ContentControl Grid.Column="1" Content="{Binding CurrentApplicationSettings}" Margin="10,0,0,0"/>
In the corresponding view model (the SetValue method calls OnPropertyChanged):
public IApplicationSettingsViewModel CurrentApplicationSettings
{
get { return GetValue<IApplicationSettingsViewModel>(); }
set { SetValue(value); }
}
In the detail view where I have the problem, the ListBoxEdit is defined like this:
<dxe:ListBoxEdit ShowBorder="False" ItemsSource="{Binding AllLoggingLevels, Mode=OneTime}" SelectedIndex="{Binding Path=LoggingLevel, Mode=TwoWay, Converter={StaticResource LogLevelConverter}, UpdateSourceTrigger=PropertyChanged}">
<dxe:ListBoxEdit.StyleSettings>
<dxe:RadioListBoxEditStyleSettings/>
</dxe:ListBoxEdit.StyleSettings>
</dxe:ListBoxEdit>
In the corresponding ViewModel:
public LoggingLevel LoggingLevel
{
get { return GetValue<LoggingLevel>(); }
set { SetValue(value); }
}
The DataTemplates are registered by code:
public void RegisterDataTemplate(DataTemplate dataTemplate)
{
object dataTemplateKey = dataTemplate.DataTemplateKey;
if (dataTemplateKey == null)
{
throw new InvalidOperationException("The data template has an invalid key");
}
System.Windows.Application.Current.Resources.Add(dataTemplateKey, dataTemplate);
}
Thank you very much
Philippe
I have receive an answer from DevExpress support and this works for me.
"Having researched it, I found that this behavior occurs because of the internal editor's synchronization.When the editor loses its items source because of changing the focused row in the tree, it resets its properties such as EditValue, Text, SelectedItem, etc.
To prevent these properties from being synchronized, set the editor's AllowUpdateTwoWayBoundPropertiesOnSynchronization property to false."

WPF listview display converter

I have a collection of objects that derive from a Person class and I want to bind this collection to the ItemsSource of a ListView.
I want to specify a string to display in the ListView Items. This string will be a composite of properties found on the derived classes.
I also want to bind the SelectedItem of the ListView to a property of type Person in my view model.
As far as I see it I need a string converter for my display string but I'm unsure how to bind to the items within the ItemsSource to generate the composite display string.
Can anyone give me a pointer?
Thanks.
You can either overwrite the ToString() method of your derived classes to return your composite display string, or you can create a Converter like you are suggesting and pass it the entire Item. The converter would then check that the item is of a specified type, and if so compose a string of whatever properties you want.
you dont need the StringConverter, you need DataTemplate
using DataTemplate, you can choose how you would like to display you data as an item in your listBox.
If you could consider your derived class a ViewModel then you could just add a property to that class and then display it in the ListView ItemTemplate. Or like Rachel suggested override your ToString Method and then in your display binding simply write "{Binding}" which will force WPF to call the ToString method
e.g.
public class DerivedPerson : Person
{
public string DisplayString
{
get
{
return string.Format("{0} {1}",FirstName,LastName);
}
}
}
And you xaml:
<ListView ItemsSource="{Binding PersonList}" SelectedItem="{Binding SelectedPerson}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding DisplayString}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Silverlight 3 data-binding child property doesn't update

I have a Silverlight control that has my root ViewModel object as it's data source. The ViewModel exposes a list of Cards as well as a SelectedCard property which is bound to a drop-down list at the top of the view. I then have a form of sorts at the bottom that displays the properties of the SelectedCard. My XAML appears as (reduced for simplicity):
<StackPanel Orientation="Vertical">
<ComboBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=Cards}"
SelectedItem="{Binding Path=SelectedCard, Mode=TwoWay}"
/>
<TextBlock Text="{Binding Path=SelectedCard.Name}"
/>
<ListBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=SelectedCard.PendingTransactions}"
/>
</StackPanel>
I would expect the TextBlock and ListBox to update whenever I select a new item in the ComboBox, but this is not the case. I'm sure it has to do with the fact that the TextBlock and ListBox are actually bound to properties of the SelectedCard so it is listening for property change notifications for the properties on that object. But, I would have thought that data-binding would be smart enough to recognize that the parent object in the binding expression had changed and update the entire binding.
It bears noting that the PendingTransactions property (bound to the ListBox) is lazy-loaded. So, the first time I select an item in the ComboBox, I do make the async call and load the list and the UI updates to display the information corresponding to the selected item. However, when I reselect an item, the UI doesn't change!
For example, if my original list contains three cards, I select the first card by default. Data-binding does attempt to access the PendingTransactions property on that Card object and updates the ListBox correctly. If I select the second card in the list, the same thing happens and I get the list of PendingTransactions for that card displayed. But, if I select the first card again, nothing changes in my UI! Setting a breakpoint, I am able to confirm that the SelectedCard property is being updated correctly.
How can I make this work???
If you are using Silverlight 3 you will need to use INotifyPropertyChanged.
Example:
public class CardViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Card> Cards { get; set; }
private Card _selectedCard;
public SelectedCard
{
get
{
return _selectedCard;
}
set
{
if (value != _selectedCard)
{
_selectedCard = value;
NotifyPropertyChanged("SelectedCard");
}
}
}
public CardViewModel()
{
Cards = new ObservableCollection<Card>();
//Populate Cards collection with objects
}
public void NotifyPropertyChanged(string item)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(item));
}
}
}
All you would need to do is set this class to your views DataContext and everything should be happy.
A pattern I've been using recently is to bind the data context of a container of detail info to the selected item of the list box. The XAML in your case becomes:
<StackPanel Orientation="Vertical">
<ComboBox x:Name="_lbxCards" <-- new
DisplayMemberPath="Name"
ItemsSource="{Binding Path=Cards}"
SelectedItem="{Binding Path=SelectedCard, Mode=TwoWay}"
/>
<StackPanel DataContext={Binding ElementName=_lbxCards,Path=SelectedItem}> <-- new
<TextBlock Text="{Binding Path=Name}" <-- updated
/>
<ListBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=PendingTransactions}" <-- updated
/>
</StackPanel> <-- new
</StackPanel>
Turns out the problem isn't in the UI at all. The PendingTransactions class lazy-loads its values using a async WCF call to the server. The async pattern uses events to notify the caller that the operation is complete so the data can be parsed into the class. Because each Card has its own instance of the PendingTransactions class and we used a ServiceFactory to manage our WCF proxies, each instance was wiring up their event handler to the same event (we are using a singleton approach for performance reasons - for the time being). So, each instance received the event each time any of the instances triggered the async operation.
This means that the data-binding was working correctly. The PendingTransactions collections were overwriting themselves each time a new Card was viewed. So, it appeared that selecting a previous card did nothing when, in fact, it was selecting the correct object for binding, it was the data that was screwed up and make it look like nothing was changing.
Thanks for the advice and guidance nonetheless!

How to bind an observable collection to Multiple user controls at runtime?

I am stucked at the part where I have to bind a collection to a dynamic usercontrol. Scenario is something like this.
I have a dynamic control, having a expander , datagrid, combobox and textbox, where combox and textbox are inside datagrid. There are already two collections with them. One is binded with combobox and another is binded with datagrid. When the item is changes in combox its respective value is set to its respective textbox, and so on. and this pair of value is then set to the collection binded with datagrid. A user can add multiple items.
Now the main problem is that all these things are happening inside a user control which is added dynamically, that is on button click event. A user can add desired numbers of user controls to the form.
problem is coming in this situtaion. Say I have added 3 controls. Now in 1st one if i add a code to the collection then it gets reflected in the next two controls too, as they are binded with same collection.
So, I want to know is there anyway to regenrate/rename the same collection so that the above condition should not arise.
It's hard to answer your question without seeing the bigger picture, however I have a feeling you are going about this the wrong way. It appears that you are adding instances of your user control directly from code. Instead of doing that, you should create some kind of ItemsControl in your XAML, and in its ItemTemplate have your user control. Bind that ItemsControl to a collection in your view model, and only manipulate that collection.
You should not be referring to visual controls in your view model or code behind. Whenever you find yourself referencing visual elements directly from code, it should raise a warning flag in your mind "Hey! There's a better way than that!"...
Example:
The view model:
public class ViewModel
{
public ObservableCollection<MyDataObject> MyDataObjects { get; set; }
public ViewModel()
{
MyDataObjects = new ObservableCollection<MyDataObject>
{
new MyDataObject { Name="Name1", Value="Value1" },
new MyDataObject { Name="Name2", Value="Value2" }
};
}
}
public class MyDataObject
{
public string Name { get; set; }
public string Value { get; set; }
}
The window XAML fragment containing the list box and the data template:
<Window.Resources>
...
<DataTemplate x:Key="MyDataTemplate">
<local:MyUserControl/>
</DataTemplate>
</Window.Resources>
...
<ListBox ItemsSource="{Binding MyDataObjects}"
ItemTemplate="{StaticResource MyDataTemplate}"
HorizontalContentAlignment="Stretch"/>
The user control:
<UniformGrid Rows="1">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Value}" HorizontalAlignment="Right"/>
</UniformGrid>

Resources