I am creating a ListView that can have some items removed from it (it's a list of favorites) by selecting them, bringing up the app bar and clicking "Remove from favorites". When the button is clicked, a method in the current view model is asked to remove this item from the list. After this happens, the UI gets updated, and the item is removed.
Now, I have two problems. The first one is that the back-button of the page receives the focus (it gets a dotted outline) when an item is removed, something which I do not want.
The second problem is that the list doesn't use the add / delete animation I've set it to use.
A solution to either of these would be appreciated.
Here is some pseudo code showing what happens:
XAML:
<GridView x:Name="FavoritesGridView"
Grid.Row="1"
SelectionMode="Multiple"
ItemTemplate="{StaticResource FavoritesOnSectionViewItemTemplate}"
ItemsSource="{Binding FavoritesList}"
ItemClick="ProgramGrid_OnItemClick"
IsItemClickEnabled="True"
SelectionChanged="FavoritesGridView_OnSelectionChanged"
ScrollViewer.HorizontalScrollMode="Disabled">
<GridView.ItemContainerStyle>
<Style TargetType="Control">
<Setter Property="Margin" Value="0,0,38,8"/>
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemContainerTransitions>
<TransitionCollection>
<AddDeleteThemeTransition/>
</TransitionCollection>
</GridView.ItemContainerTransitions>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Vertical" MaximumRowsOrColumns="9" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
Codebehind:
private void UnFavoriteButton_Click(object sender, RoutedEventArgs e)
{
viewModel.RemoveFromFavorites(FavoritesGridView.SelectedItems.Cast<FavoriteProgram>().AsEnumerable());
}
ViewModel:
public void RemoveFromFavorites(IEnumerable<FavoriteProgram> programs)
{
FavoriteController.RemoveFromFavorites(programs);
UpdateUi();
}
private void UpdateUi()
{
OnPropertyChanged("FavoritesList");
}
public IEnumerable<FavoriteProgram> FavoritesList
{
get { return CoreData.TvFavorites; } // A centralized list
}
FavoritesController:
public static void RemoveFromFavorites(IEnumerable<FavoriteProgram> programs)
{
if (programs.IsNullOrEmpty()) return;
foreach (var program in programs)
RemoveFromFavorites(program);
}
public static void RemoveFromFavorites(FavoriteProgram program)
{
if (!IsFavorite(program)) return;
var list = CoreData.TvFavorites.ToList();
list.Remove(program);
CoreData.TvFavorites = list.AsEnumerable();
}
Any ideas?
I see. So you have two problems.
[1]. The back button receives focus.
It is my opinion that the Back button should never receive focus. There is already a key gesture to go back, so setting focus is silly. Why it wasn't already disabled to have focus, I do not know. Here's all you do:
<Button TabIndex="-1" Style="{StaticResource BackButtonStyle}" />
Or you can do it with a style:
<Grid Background="Black">
<Grid.Resources>
<Style TargetType="Button" BasedOn="{StaticResource BackButtonStyle}" x:Name="MyBackButtonStyle">
<Setter Property="TabIndex" Value="-1" />
</Style>
</Grid.Resources>
<Button Style="{StaticResource MyBackButtonStyle}" />
</Grid>
Using this new style (or just updating the existing one) will result in the back button never receiving focus. If you want it to be able to receive focus, for some reason, then the solution would be to handle the GotFocus event and simply use (sender as Button).Focus(FocusState.Unfocused);. To be fair, you should also determine why you would be removing focus.
[2]. The animations are not happening
This is a common problem. The reason is, you do not want to setup animations on the ListView, you want to set up the animations on the ListView ItemsPanel. Here's all you want to do:
<ListView>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel>
<StackPanel.ChildrenTransitions>
<AddDeleteThemeTransition />
</StackPanel.ChildrenTransitions>
</StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
It's as simple as that. (my sample is a StackPanel, remember to use a WrapGrid as you have in your code) You just had the transitions in the wrong place. So, now you can handle the focus problem you are having and you can get the transitions you want.
I might offer a bit of advice. Since you are using view models, it sounds strange to hear that you are not also using delegate commands. If you want to use MVVM at its best, delegate commands solve a lot of problems for you. Read here: http://blog.jerrynixon.com/2012/08/most-people-are-doing-mvvm-all-wrong.html
And for a second bit of advice. It sounds to me like you might be using the default templates from Visual Studio. Most developers start there. The problem is that those templates are not very great to teach best practices. My suggestion: don't be afraid of the blank template.
Best of luck!
Now, I have two problems. The first one is that the back-button of the
page receives the focus (it gets a dotted outline) when an item is
removed, something which I do not want.
This problem could be solved, by adding a TwoWay-Binding to the SelectedItem property of the GridView. After removing the favorite programms set the SelectedItem per code, so that it gets focused in the GridView.
XAML:
<GridView x:Name="FavoritesGridView"
SelectedItem="{Biding SelectedFavorite, Mode=TwoWay}" />
C#:
private FavoriteProgram _selectedFavorite;
public FavoriteProgram SelectedFavorite {
get {
return _selectedFavorite;
}
set {
_selectedFavorite = value;
OnPropertyChanged("SelectedFavorite");
}
}
After removing your items, set the property SelectedFavorite to an item in your FavoritesList.
public void RemoveFromFavorites(IEnumerable<FavoriteProgram> programs) {
FavoriteController.RemoveFromFavorites(programs);
UpdateUi();
SelectedItem = FavoritesList.FirstOrDefault(); // selects the first element in list.
}
The second problem is that the list doesn't use the add / delete
animation I've set it to use.
The problem here is that you always use a new collection/list for your property CoreData.TvFavorites after you have removed your favorites and therefore can the GridView not determine which items have been removed or added.
For binding scenarios there is a specialized collection named ObservableCollection<T>, that implements the interface INotifyCollectionChanged. The interface defines an event to notify (UI Elements) that items are added or removed from the collection. You should change your property FavoritesList to type ObservableCollection<FavoriteProgramm> and update the collection in your UpdateUI method to remove the relevant favorites.
private void UpdateUi()
{
//Update your FavoritesList to enable animations.
OnPropertyChanged("FavoritesList");
}
private ObservableCollection<FavoriteProgram> _favorites;
public ObservableCollection<FavoriteProgram> FavoritesList
{
get {
if (_favorites == null) {
_favorites = new ObservableCollection<FavoriteProgram>();
}
return _favorites;
}
}
Related
Alright, I'm fairly new to WPF and I ran into a very strange problem. The relevant section of my XAML defines a Border around a ScrollViewer around a StackPanel which is populated using an ItemsControl that is then databound to a CollectionViewSource which in turn wraps a standard ObservableCollection. The ItemsControl defines a DataTemplate that contains only one tag: a custom control I've made called a StackElement. I'm handling three events from this control — MouseEnter, MouseLeave, and PreviewMouseLeftButtonUp. These events can fire, but do so unreliably.
For example, after some new StackElements are added, the MouseEnter event generally doesn't fire on the first StackElement until I've moused over a few others. Once a MouseOver manages to fire once, it continues to fire correctly on that StackElement from there on out.
However, the first time mousing over a StackElement doesn't always fail. If I approach the StackElements from beneath and try the last one first, it will always fire. When I do this, sometimes the first one will work, but the second one won't fire. Once, both of them did manage to operate correctly, but it happens infrequently.
I'm not multithreading anything, none of my parent controls handle events of their own, all event handlers consist only of a WriteLine() statement for debugging purposes, and the StackElement code-behind isn't handling any events either.
I've tried decoupling the ItemsControl from the CollectionViewSource in favor of binding it directly to the ObservableCollection, which did nothing other than (as I expected) bypass the sorting functionality I added to the ViewSource. I tried handling the events in the StackElement class itself, in addition to making them be tied to other controls contained within StackElement. I tried using DataTriggers, which if I remember worked as expected, but I need to include more advanced logic such as multiselection and the inability to lightly highlight an already-selected StackElement.
For context, I'm intending to use these events to lightly highlight StackElements when the user drags the mouse over them and to strongly highlight them when the mouse is pressed — basically, I need something that looks and feels like Windows File Explorer. From what I've seen this can't be accomplished in an elegant fashion with DataTriggers alone.
Here's my event handlers (in MainWindow.xaml):
private void StackElement_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("OnPreviewMouseLeftButtonUp fired for a StackElement.");
}
private void StackElement_OnMouseEnter(object sender, MouseEventArgs e)
{
Console.WriteLine("OnMouseEnter fired for a StackElement.");
}
private void StackElement_OnMouseLeave(object sender, MouseEventArgs e)
{
Console.WriteLine("OnMouseLeave fired for a StackElement.");
}
Here's how I'm adding to the bound collection (for testing, which is why it's hooked up to a random button):
private void Btn_File_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
InitiativeStackElement t = new InitiativeStackElement(new Entity("TestName", 10, 11, 12, 13, null)); //InitiativeStackElement implements INotifyPropertyChanged so the databindings work
_entityProvider.Elements.Add(t); //_entityProvider is just a reference to a XAML-defined resource class, which is loaded up in the constructor so I don't have to call TryGetResource() whenever I want to use it. it's currently used for testing purposes only
}
Finally, here's the portion of my XAML containing the StackElements:
<Border Grid.Row="1"
Margin="0,1,0,0"
Style="{StaticResource StandardBorder}">
<ScrollViewer Name="Scv_InitiativeStack">
<StackPanel Name="Stp_InitiativeStack">
<ItemsControl Name="Its_InitiativeStack" ItemsSource="{Binding Source={StaticResource SortedInitiativeStack}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<con:StackElement Element="{Binding}" PreviewMouseLeftButtonUp="StackElement_OnPreviewMouseLeftButtonUp" MouseEnter="StackElement_OnMouseEnter" MouseLeave="StackElement_OnMouseLeave"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Border>
The StackElement class just defines a single DependencyProperty of type InitiativeStackElement. The properties of this object are bound to a few controls within the StackElement, which always displays correctly. It's the behavior of the events that have me confused.
As described, I'm expecting the MouseEnter event to fire whenever the mouse is dragged onto the StackElement. However, it's only firing after I fulfill seemingly random conditions that shouldn't affect it's functionality, like mousing over another StackElement first. There are no error messages.
Alright, I was able to get the functionality I wanted using ListBox:
<Window.Resources>
<DataTemplate x:Key="InitiativeStackTemplate">
<con:StackElement Element="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Border Margin="0,1,0,0"
Grid.Row="1"
Style="{StaticResource StandardBorder}">
<ScrollViewer Name="Scv_InitiativeStack">
<ListBox Name="Lbx_InitiativeStack"
SelectionMode="Extended"
ItemsSource="{Binding Source={StaticResource SortedInitiativeStack}}"
ItemTemplate="{StaticResource InitiativeStackTemplate}"
HorizontalContentAlignment="Stretch"/>
</ScrollViewer>
</Border>
Everything works as expected.
I've put together what I thought was a context menu in an MVVM setting (I'm using WPF with XAML and C#, using MVVM). Only it's not working, which is why I'm here. I'm getting nothing in my context menu.
The XAML is supposed to call an ICommand in the code behind (or Relay Command since I'm using micro MVVM - same thing basically).
The first thing was to set up an object which the XAML could get the two needed values from - the Header and the Command. The item in question looks like this:
class ContextMenuVM : ObservableObject
{
public string Displayname { get; set; }
public RelayCommand ContextMenuCommand { get; set; }
}
So, rather simple there. These will be used for the bindings in the menu.
The view model here is called 'CharacterListViewModel' and contains an ObservableCollection if these ContextMenuVM objects. That looks like this:
private ObservableCollection<ContextMenuVM> _sceneAddMenu = new ObservableCollection<ContextMenuVM>();
public ObservableCollection<ContextMenuVM> SceneAddMenu
{
get { return _sceneAddMenu; }
set
{
if (_sceneAddMenu != value)
{
_sceneAddMenu = value;
RaisePropertyChanged("SceneAddMenu");
}
}
}
The ObservableCollection is populated, as follows:
foreach (Scene s in Database.Instance.Scenes)
{
SceneAddMenu.Add(new ContextMenuVM()
{
Displayname = s.SceneName, ContextMenuCommand = new RelayCommand(
() =>
{
MessageBox.Show("Clicked");
})
});
}
Just a test at the moment, but I can say through use of break points that SceneAddMenu contains four items after this code is run (as I would expect).
Well, that's kind of the background code. I suspect it works, although clearly something is broken. My suspicion is the XAML.
The context menu code itself is here:
<ContextMenu x:Key="CharacterMenu" ItemsSource="{Binding SceneAddMenu}">
<ContextMenu.ItemTemplate >
<DataTemplate DataType="MenuItem">
<MenuItem Header="Edit" Command="{Binding ContextMenuCommand}"></MenuItem>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
Ah, so the obvious problem would be that the data context is not properly set up. Well that's not the case because this context menu replaces another one which utilised a command in the view model (and that worked), so my assumption is that the view model is okay.
For the record, the previous context menu, which works, looked like this:
<ContextMenu x:Key="CharacterMenu">
<MenuItem Header="Edit" Command="{Binding EditCharacter}"/>
</ContextMenu>
And if I put that back in, it works. Since it has a binding to the view model, that would suggest that the data context is not the problem.
The Context menu itself is referenced a little later, like this:
<StackPanel Orientation="Horizontal" Margin="3" ContextMenu="{StaticResource CharacterMenu}">
But since that was also in before with the previous menu (i.e. when it worked), I only include it for completion's sake.
So the SceneAddMenu object (ObservableCollection) is populated. That seems to be fine. Somewhere between the XAML and the view model there must be a problem though. If I put a break point in the 'get' for SceneAddMenu and then right click on the item in question, the break point does not activate.
I am at a bit of a loss on this one. It's my first time creating a context menu using the MVVM method, so it's possible I missed out a step somewhere.
If you read all of this, thanks a lot. If I missed out any information, please let me know.
You shouldn't add a MenuItem to the ItemTemplate of a ContextMenu. You should define an ItemContainerStyle and bind to the Displayname and ContextMenuCommand properties of your class:
<ContextMenu x:Key="CharacterMenu" ItemsSource="{Binding SceneAddMenu}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Displayname}" />
<Setter Property="Command" Value="{Binding ContextMenuCommand} " />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
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.
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.
This is probably a really easy fix, but I am inexperienced in working with events so I am just going to ask it.
I have a ListBox with ListBoxItems in it. These ListBoxItems will be binded to a data source so they will change. I need a MouseDown event to be raised when a MouseDown action is performed on any of these ListBoxItem (because I am doing drag and drop). Since the values are changing, I cannot expect to wire the events together in the XAML like the following
<ListBox Name="ListBox1">
<ListBoxItem MouseDown="MouseDownEventName">Item A</ListBoxItem>
<ListBoxItem MouseDown="MouseDownEventName">Item B</ListBoxItem>
<ListBoxItem MouseDown="MouseDownEventName">Item C</ListBoxItem>
</ListBox>
This would be easy if I had static values, but since the values in the ListBox will change, I would prefer to write the following XAML
<ListBox Name="ListBox1" MouseDown="MouseDownEventName">
//Binded Values
</ListBox>
Then, when the ListBoxItem is selected, it would Bubble the event up to this MouseDownEventName, and I can grab ListBox1.SelectedItem at that time, the problem is, I am trying this right now, but it is not working. I have the following Code to handle the MouseDown, which is only rewriting label content at the moment to signify that the item has been MouseDown'ed.
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void ListBox_MouseDown(object sender, RoutedEventArgs e)
{
ListBox box = (ListBox)sender;
if (box != null && box.SelectedItem != null)
{
DragDrop.DoDragDrop(box, ItemListBox.SelectedItem, DragDropEffects.Move);
label1.Content = "MouseDown Event Fired";
}
}
}
Using XAML, you can provide a template for different types contained within the form. For example, in this case, you can specify that ListBoxItem's fire off a certain event handler. This is the bulk of the XAML markup code (details found here: How to catch Click on ListboxItem when the item is templated?)
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ListBoxItem_PreviewMouseLeftButtonDown"/>
</Style>
</ListBox.ItemContainerStyle>
...
</ListBox>
Another thing to check before is to try and change your DragDrop.DoDragDrop() method call to something else to see if the issue has to do with that method. Since the Label's content is changing, I would imagine it has something to do with that method.