WPF ListBox binding works at first, but not afterwards - c#

Base Problem => my ListBox won't update when a different ComboBox value is selected
I'm making a WPF application following the MVVM pattern (or at least trying to). I have a list of servers that I fetch based on the currently selected
"Application". The list of servers are put into a ListBox, and I want the ListBox to update with the new servers when I changed the "Application" via a dropdown menu.
The "Current:" value will change based on the selection made (so that binding at least works). I have a ServerListViewModel class which implements INotifyPropertyChanged. Here's a snippet of it
ServerListViewModel (snippet)
public ServerListViewModel()
{
_serverListModel = new ServerListModel
{
ServerList = ConfigUtility.GetServers()
};
}
...
public BindingList<string> ServerList
{
get { return _serverListModel.ServerList; }
set
{
if (ReferenceEquals(_serverListModel.ServerList, value)) return;
_serverListModel.ServerList = value;
InvokePropertyChanged("ServerList");
}
}
The constructor properly works properly and the ListBox updates to reflect the ServerList property. 'ConfigUtility.GetServers' uses a saved value of the active "Application" in a JSON file.
In this class I setup a static property like this so that I could try to access this class from another view model
public static ServerListViewModel Instance { get; } = new ServerListViewModel();
SettingsViewModel.cs
The dropdown menu is on a settings tab, while the server list has its own tab. These tabs have their own view models.
Here's a snippet of this view model:
public ComboBoxItem CurrentApplication
{
get { return _settingsModel.CurrentApplication; }
set {
SetCurrentApplication(value);
}
}
private void SetCurrentApplication(ComboBoxItem value)
{
if (ReferenceEquals(_settingsModel.CurrentApplication, value)) return;
_savedSettings = MySettings.Load();
if (value.Content == null)
{
_settingsModel.CurrentApplication =
new ComboBoxItem { Content = _savedSettings.CurrentApplication };
}
else
{
_settingsModel.CurrentApplication = value;
_savedSettings.CurrentApplication = (string)value.Content;
_savedSettings.Save();
ServerListViewModel.Instance.ServerList = ConfigUtility.GetServers();
}
InvokePropertyChanged("CurrentApplication");
}
I also have a MySettings object which saves values to a JSON file so that I can save the active "Application" b/w sessions.
XAML
<TabControl DockPanel.Dock="Bottom" HorizontalAlignment="Stretch" Height="Auto" TabStripPlacement="Bottom">
<!-- Server List -->
<TabItem Name="ServerListTab" Header="Server List">
<TabItem.DataContext>
<viewModel:ServerListViewModel />
</TabItem.DataContext>
<ListBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{Binding ServerList, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedServer}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="FontSize" Value="14"></Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</TabItem>
<!-- Settings -->
<TabItem Name="SettingsTab" Header="Settings">
<TabItem.DataContext>
<viewModel:SettingsViewModel />
</TabItem.DataContext>
<StackPanel>
<TextBlock FontWeight="Bold">Application</TextBlock>
<WrapPanel>
<TextBlock>Current:</TextBlock>
<TextBlock Text="{Binding CurrentApplication.Content}"></TextBlock>
</WrapPanel>
<TextBlock>Select</TextBlock>
<ComboBox Name="TheComboBox" SelectedItem="{Binding CurrentApplication}">
<ComboBoxItem>NetWebServer</ComboBoxItem>
<ComboBoxItem>NetSearchService</ComboBoxItem>
<ComboBoxItem>NetInterface</ComboBoxItem>
</ComboBox>
<TextBlock FontWeight="Bold">Service</TextBlock>
<WrapPanel>
<TextBlock>Current:</TextBlock>
<TextBlock></TextBlock>
</WrapPanel>
<TextBlock>Select</TextBlock>
<ComboBox></ComboBox>
<CheckBox>
Perfomance mode
</CheckBox>
<CheckBox>
Show live servers
</CheckBox>
<CheckBox>
Show test servers
</CheckBox>
<CheckBox>
Show only parameters with IP's
</CheckBox>
</StackPanel>
</TabItem>
</TabControl>
I haven't separate these into separate views yet. Is the problem they are not separate user controls in different files with their own view models? I have set the different tab items to the corresponding view model however.
Conclusion/Question
How can I get it so that by selecting a new item in the ComboBox, it also updates the server list? By debugging my code, it seems everything works line by line so somehow the ListBox must not be notified of a change? If I close the app with a different "Application" selected, then open it back up, it correctly uses the saved setting to populate the ListBox with the new values. But while the app is open I cannot get the ListBox to change. Any ideas?

In your XAML, you create a ServerListViewModel object, which is being set as DataContext for a TabItem.
However, in the method SetCurrentApplication you update the ServerList property of another ServerListViewModel object (the initializer of this static property created it). You don't see any change in the UI because the ServerListViewModel object used as DataContext has actually not been touched.
Simply use the ServerListViewModel object from the static ServerListViewModel.Instance property as your DataContext:
<TabItem
DataContext="{x:Static viewModel:ServerListViewModel.Instance}"
Name="SettingsTab" Header="Settings"
>
...
Now, updating ServerListViewModel.Instance.ServerList within the SetCurrentApplication method should cause the data bindings to update.
Another, cleaner alternative is to eschew that static Instance property and use some kind of messaging between the view-models. The SetCurrentApplication method could send a message that the server list should be updated. The ServerListViewModel class would process that message and do the actual work of updating the server list. MVVM libraries such as MVVM Light and others provide Messenger components which facilitate this.

If I follow everything, SetCurrentApplication() would need to refresh the server list and pop an INotifyChanged event. It doesn't look like its doing that.

Related

No update of ListBox.ItemsSource after implementing editable ListBox items via DataTemplate

I implemented editable ListBox items like it is posted in this answer Inline editing TextBlock in a ListBox with Data Template (WPF)
.
But the new value does not get updated in the ItemsSource object of my ListBox.
This is the XAML:
<ListBox Grid.Row="2" Name="ds_ConfigProfiles" ItemsSource="{Binding ConfigProfiles}" SelectedItem="{Binding ActiveConfigProfile}" IsSynchronizedWithCurrentItem="True" Panel.ZIndex="-1">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- TODO: this is meant for allowing edit of the profile names, but the new name does not get stored back to ConfigProfiles -->
<local:TextToggleEdit Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="40" Height="23" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is the ConfigProfiles property in the view model:
/// <summary>Configuration profiles that were found in the active storage path</summary>
public ObservableCollection<string> ConfigProfiles { get; private set; } = new ObservableCollection<string>();
Did I understand something wrong?
May it be the reason, that the items source is of type ObservableCollection<string> instead of ObservableCollection<ProperClassImplementation> (which is of legacy reasons).
I am relatively new to WPF and am out of ideas on how to debug this.
May it be the reason, that the items source is of type ObservableCollection<string> instead of ObservableCollection<ProperClassImplementation> (which is of legacy reasons).
Yes, exactly. You can't modify a string since it is immutable. You need to bind to a string property of a class which means that you need to replace the ObservableCollection<string> with an ObservableCollection<ProperClassImplementation>.
I am afraid the binding engine won't replace the string in the ObservableCollection<string> with a new string for you if that's what you had hoped for.

How and who's changing the model of a hierarchical (TreeView) ViewModel (MVVM)

I'm working on a small "fun" project using WPF/MVVM/Prism which main component is a TreeView showing the file structure of a certain path. The idea how the ViewModel works is taken from Josh Smiths' article http://www.codeproject.com/Articles/28306/Working-with-Checkboxes-in-the-WPF-TreeView.
I actually need two things:
I'd like to get a list of checked items of the TreeView-View, presented in a another view (let's say List-View) and also show that the their state has changed.
I'd like to modify the List-View, which shall then be reflected within the TreeView.
Somehow I did not find a nice solution, as the hierarchical ViewModel used by Josh makes it hard for me to get hold on a "shared" model useable within both ViewModels.
But let's have a look at my code before stating my question:
My "ExplorerView" uses a hierarchical datatemplate and looks as follows:
<UserControl x:Class="MyFunProject.Views.ExplorerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:MyFunProject.ViewModels"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<UserControl.Resources>
<HierarchicalDataTemplate DataType="{x:Type ViewModel:ItemBaseViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="True" IsChecked="{Binding IsChecked}" VerticalAlignment="Center" ToolTip="{Binding Path}">
<TextBlock Text="{Binding Name}" />
</CheckBox>
</StackPanel>
</HierarchicalDataTemplate>
</UserControl.Resources>
<TreeView ItemsSource="{Binding Children}" />
</UserControl>
Following Josh's article, the ExplorerViewModel exhibits a child as a List<CheckableItemViewModel> with only one entry - which in fact has other childs directories or files. Directories itself again have childs.
public class ExplorerViewModel : BindableBase
{
private List<CheckableItemViewModel> childred;
public List<CheckableItemViewModel> Children
{
get { return childred; }
set { SetProperty(ref childred, value); }
}
public ExplorerViewModel(IExplorerModel ExplorerModel)
{
CheckableItemViewModel root = new CheckableItemViewModel();
AddChildrenToNode(root, ExplorerModel.GetCheckableItems());
root.Initialize(); // initialize children (and each children its own children, etc...)
Children = new List<CheckableItemViewModel> { root });
}
private void AddChildrenToNode(CheckableItemViewModel root, IList<CheckableItem> items)
{
foreach(var item in items)
{
var child = new CheckableItemViewModel(item);
var dirItem = item as DirectoryItem; // it's a directory and so it has childs
if(dirItem != null)
{
AddChildrenToNode(child, dirItem.Items);
}
root.Children.Add(child);
}
}
}
And now to my questions:
How do I connect my CheckableItemViewModel to a "kind of" global CheckableItemModel? If I inject the constructor by resolving a registered instance (unity container) I've got the problem, that I cannot do that if I'd like to have two ExplorerViews simultanously (or don't I?).
How do I inject if every CheckableItemViewModel also needs constructor parameters (is this the case where to use parameter override?)
How do I get a retrieve a list of the actual selected items (or when and where should I update the according model)?
How do I get a "changed" flag if one of the CheckableItemViewModel is altered?
Let me know if i missed a piece of my puzzle.
Thanks for any suggestions.

MVVM - Behavior with Parameters or Find Specific Parent/UserControl Resources

So, I am fairly new to MVVM and have backed myself into an interesting corner where I am not sure how to make things work with either a behavior or a command. I have a user control that contains a listbox of items which need to implement various behaviors such as deleting or removing the given item. Like so:
<UserControl> // DataContext is a viewmodel
// Borders, grids, various nesting controls...
<ListBox x:Name="ListBox_Items" ItemSource="{Binding ItemsList}">
<ListBox.ItemTemplate>
<DataTemplate> // From here on the individual item has its own data context of type Item in ItemsList
<StackPanel Orientation="Horizontal">
<TextBox Name="EditItemStuffOnLoseFocus" Text="{Binding ItemStuff}"/>
<Button Name="DeleteItemStuff"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
The example has been simplified, but the basic idea is that the textbox should edit its associated listbox item when it loses focus and the button should delete the associated listbox item when pressed. At first I implemented commands for this and had both working, until I realized that I had forgotten the standard "Are you sure?" message. I added this in the command, but since it has no concept of the actual objects, I can't think of how to tell it where to put the dialog window. The command accepts a view model (_ViewModel) on creation and accepts the Item model (textbox/button's DataContext) as a parameter. With the basic message box dialog, the Execute() method looked something like this (simplified):
public void Execute(object parameter)
{
if (MessageBox.Show("Really delete the item?", "Delete Item", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
ItemService service = new ItemService();
service.RemoveItem(((Item)parameter).ItemID);
if (_ViewModel.ReloadItemListCommand.CanExecute(_ViewModel.ItemInfo))
_ViewModel.ReloadItemListCommand(_ViewModel.ItemInfo);
}
}
Of course, this message box is not centered on the application, which is small but annoying. A coworker suggested that I replace the Command with a Behavior so that I would have an associated object to use for centering the message box. The problem is, I haven't been able to find any information on passing parameters to a behavior, or how to trace back multiple levels from an associated object to its parents so that I can get the view model for the reloading step as well as the individual item's model (the associated object's DataContext).
In summary, is there a way to either center the MessageBox on the application within the command while remaining MVVM-friendly, OR to pass parameters / retrieve a specific parent object or its resources using a behavior?
____________ UPDATE ____________
The answer below works great, but I went another route so that I could use DataContext variables in my MessageBox. I managed to preserve access to the DataContext of the calling control and the view model by adding the view model to the control's tag:
<UserControl> // DataContext is a viewmodel
// Borders, grids, various nesting controls...
<ListBox x:Name="ListBox_Items" ItemSource="{Binding ItemsList}">
<ListBox.ItemTemplate>
<DataTemplate> // From here on the individual item has its own data context of type Item in ItemsList
<StackPanel Orientation="Horizontal">
<TextBox Name="EditItemStuffOnLoseFocus" Text="{Binding ItemStuff}" Tag={Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext}"/>
<Button Name="DeleteItemStuff" Tag={Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
I'm not entirely certain this is the best way to be using Tag, but it does get all the information I need into the behavior while allowing me to center the MessageBox. The behavior is similar to the command except for a few added lines to extract the model and view model. Keeping with the initial shortened example, it looks something like this:
ExampleViewModel viewModel = (ExampleViewModel)AssociatedObject.Tag;
Item parameter = (Item)AssociatedObject.DataContext;
if (MessageBox.Show(Window.GetWindow(AssociatedObject), "Really delete the item?", "Delete Item", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
ItemService service = new ItemService();
service.RemoveItem(((Item)parameter).ItemID);
if (viewModel.ReloadItemListCommand.CanExecute(viewModel.ItemInfo))
viewModel.ReloadItemListCommand(viewModel.ItemInfo);
}
}
Thank you all for the help.
To center a message box on a window will require you to either implement your own Window and do a ShowDialog where you can specify the location. Or you can inherit from the Forms control done in this CodeProject Solultion.
However for the first part of your problem It would most likely be easier to implement a click handler on the button and bind the delete to your user control as a dependency property. This would allow you to have access to the sender and keep the UI compeltely inside the control.
xaml
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
<Button Click="Button_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code Behind
public ICommand DeleteItem
{
get { return (ICommand)GetValue(DeleteItemProperty); }
set { SetValue(DeleteItemProperty, value); }
}
public static readonly DependencyProperty DeleteItemProperty = DependencyProperty.Register("DeleteItem", typeof(ICommand), typeof(control), new PropertyMetadata(null));
private void Button_Click(object sender, RoutedEventArgs e)
{
if (DeleteItem != null)
{
var result = System.Windows.MessageBox.Show("WOULD YOU LIKE TO DELETE?", "Delete", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
{
var fe = sender as FrameworkElement;
if (DeleteItem.CanExecute(fe.DataContext))
{
DeleteItem.Execute(fe.DataContext);
}
}
}
Just have your delete command bind from the outside and do the logic for your message box in the click event.

How to set ItemsSource for a databinding from the code-behind

I cannot figure out how to set ItemsSource to my Pivot programatically. I am using MVVM Light ViewModelLocator where my ViewModel is registered. Then I set the DataSource of my Page and in the xaml of the Pivot I set its ItemsSource. But in the ViewModel I have other collection that I want to change at runtime to be an ItemsSource for my Pivot:
Here is my ViewModelLocator:
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<TripTypeViewModel>();
}
public TripTypeViewModel TripTypeVM
{
get
{
return ServiceLocator.Current.GetInstance<TripTypeViewModel>();
}
}
public static void Cleanup() {}
}
XAML of the page:
<Page
...
DataContext = "{Binding Source={StaticResource Locator}, Path=TripTypeVM }">
...
<Pivot x:Name="TripsSegmentsPivot" Title="Locator" Foreground="#FF888888" Style="{StaticResource PivotStyle1}" SelectionChanged="Pivot_SelectionChanged" Margin="0" Grid.Row="1" ItemTemplate="{StaticResource TripTypeTemplate1}" ItemsSource="{Binding TripTypeViewModelDataSource}">
<Pivot.HeaderTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding id}"/>
</Grid>
</DataTemplate>
</Pivot.HeaderTemplate>
</Pivot>
So in the ViewModel except TripTypeViewModelDataSource i have also TripTypeViewModelDataSource2.
In the xaml.cs of the View I would usually do this:
TripsSegmentsPivot.ItemsSource = ViewModelLocator.TripTypeVM.TripTypeViewModelDataSource;
TripsSegmentsPivot.ItemsSource = ViewModelLocator.TripTypeVM.TripTypeViewModelDataSource2;
but its not working..
There are many ways to bind but let's discuss a few...
Directly to the Pivot object itself: http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemssource(v=vs.105).aspx
MyPivot.Itemsource=mycollection;
You could use a collection view source: http://msdn.microsoft.com/en-us/library/system.windows.data.collectionviewsource(v=vs.110).aspx Visual Stuidio will create these if you drag a container control onto the surface, it also put code in the code behind so you can "wire-it-up" It works very nicely with any collection type and fully integrates with LINQ.
You can set up observable collection in the View Model and Bind to them from the view. The easiest way to do this is to create a Static Instance of the View Model in the View XAML itself because visual studio property page will "See" the view model and allow you to pick the property as an item source. All you need to do is implement INPC or create a Dependency Property (where needed) and you are all set to go. You need this to notify the view when a property changes. https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=using%20inotifypropertychanged
If you need to change collections at run time, the CollectionViewSource is dead-simple to use.. like this...
MyCollectionViewSource.Source = MyCollection.Where(p=>p.Name==SelectedName).ToList();
MyCollectionViewSource.Source = MyCollection.Where(p=>p.ID > 500).ToList();
MyCollectionViewSource.Source = MyOtherCollection.ToList();
MyCollectionViewSource.Source = JustSayNoToObamaCare();
In the current code it shows:
ItemsSource="{Binding TripTypeViewModelDataSource}"
If you use a CollectionViewSource like this:
ItemsSource="{Binding MyCVS}"
You can then change the collection at will in the viewmodel like this.
MyCVS.Source = Collection1;
MyCVS.Source = Collection2;

Bind a collection to a WPF ListBox

Update: I've updated the code based on your help so far, and still no luck. When the application loads the ListBox has no items. I assign junk values to Customers in the windows's contructor, and then am also trying to set the ListBox's DataContext as follows:
CustomerList.DataContext = Customers;
--- Original Question (with updated code) ---
I'm having trouble with databinding in a WPF project.
I have a class, Customer, as follows:
public class Customer
{
public String Name { get; set; }
public String Email { get; set; }
}
In my XAML's code behind I have a collection of customers as follows:
public List<Customer> Customers { get; set; }
I'm trying to bind each customer to a ListBox with a ListItemTemplate displaying the customer's information (name/email) in TextBoxes along with a button which locks/unloacks the TextBoxes (sets the IsEnabled property to true or false).
What's the best way to go about this?
So far I've been tryingt he following with no success.
In the XAML I currently have the following (ignoring the toggle part for now, I'm just trying to get the collection itself to be listed.):
<Window.Resources>
<CollectionViewSource x:Key="Customers" Source="{Binding Path=Customers, Mode=TwoWay}"/>
<DataTemplate x:Key="Customer">
<StackPanel Orientation="Horizontal">
<TextBox Content="{Binding Name}" />
<TextBox Content="{Binding Email}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource Customers}}"
ItemTemplate="{StaticResource ResourceKey=Customer}"
Name="CustomerList"
Height="300" />
</StackPanel>
You need to change
ItemsSource="{Binding Source=Customers}"
to
ItemsSource="{Binding Source={StaticResource Customers}}" DataContext="{StaticResource Customers}"
Code similar to the updated one works for me after changing
<TextBox Content="{Binding Name}" />
to
<TextBox Text="{Binding Name}" />
As TextBox doesn't have Content property(like a Label), the former refused to compile in VS.
Well, it is set to Text in definition:
[ContentPropertyAttribute("Text")]
public class TextBox : TextBoxBase, IAddChild
But I thought it is only used between the brackets(<TextBox>Like so</TextBox>)?
Could this be the source of the problem?
Try setting the ItemsSource of your CustomerList as follows: ItemsSource="{Binding}". You've set the DataContext of the ListBox to the list of customers, you need to set the ItemsSource to the same collection, hence, the direct binding.
Another thing that you can do, in case you prefer to use the CollectionViewSource, is to set the DataContext of your window to the same class DataContext=this, because without this, the resource definition won't be able to locate the "Customers" collection that you defined in the code behind. If you do this, however, you don't need CustomerList.DataContext = Customers; because you're directly assigning the ItemsSource to a static resource, not relatively to the DataContext.
One more thing. I think you should give the CollectionViewSource and the corresponding collection in the code behind different names. This isn't going to cause a runtime issue, but it makes it hard to maintain the code ;)
Hope this helps :)

Categories

Resources