Changing DataContext when another ListViewItem is selected - c#

I've built an interface formed by a ListView and a panel with a few textboxes. In order to make change the context in those textboxes when another ListViewItem is selected, I've captured the SelectionChange event, to change accordingly DataContexts of the textboxes. Something like this:
void customersList_SelectItem(object sender, SelectionChangedEventArgs e)
{
Customer customer = (Customer)customersList.Selected;
if (customer != null)
{
addressField.DataContext = customer;
phoneField.DataContext = customer;
mobileField.DataContext = customer;
webField.DataContext = customer;
emailField.DataContext = customer;
faxField.DataContext = customer;
...
}
}
Now, I wonder, is this the best the way to do it? Looks like a bit forced, but I can't devise any better.

If the textboxes were all contained in a containing element (e.g. a Grid), then you could just set the DataContext of the Grid element instead. This would be cleaner.
Even better yet would be to use XAML binding and MVVM, and this code could be implemented declaratively in XAML.

Bind the dependent controls DataContext property to the ListBox's SelectedItem property.
Or better yet if they are within a container control - Set its data context once and have the children inherit it. Something like...
<StackPanel DataContext="{Binding ElementName=ListBoxName, Path=SelectedItem}">
<!--- dependent controls -->
</StackPanel>

You can also use the "/" binding path syntax in WPF in combination with a CollectionView:
<Window ... xmlns:local="...">
<Window.DataContext>
<local:MyViewModel ... />
</Window.DataContext>
<Window.Resources>
<CollectionViewSource x:Key="ItemsView" Source="{Binding Path=Items}" />
<Window.Resources>
<ListView ItemsSource="{Binding Source={StaticResource ItemsView}}">
...
</ListView>
<Grid DataContext="{Binding Source={StaticResource ItemsView}, Path=/}">
...
</Grid>
</Window>
To quickly explain this setup:
The window's datacontext is set to an instance of a view model
A CollectionViewSource is created as a resource and uses a collection exposed by the view model as its source
The listview's ItemsSource is bound directly to the CollectionView (exposed by CollectionViewSource)
The Grid (which would contain your form elements) is bound to the CurrentItem of the CollectionView via the "/" binding path syntax; each time an item is selected in the list view, the Grid's datacontext is automatically set to the currently selected item
I prefer this type of binding over having to reference specific elemetns and properties and relying on the built in power of WPF's binding and CollectionView classes.

Related

ReactiveUI + WPF: ListBox items are displayed with correct view except when inside a custom UserControl

I have some view
public class MyItem : ReactiveUserControl<MyItemViewModel> {...
with some corresponding xaml and the below call sets up the viewmodel to view registration for me
Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetCallingAssembly());
When I bind the ItemsSource property of a ListBox, the items are displayed with the registered view. However, when I define a UserControl that includes a ListBox
<UserControl x:Class="MyProject.Views.NavItem"
...
<StackPanel>
...
<ListBox ItemsSource="{Binding Path=ItemsSource}">
</ListBox>
</StackPanel>
</UserControl>
and bind to its ItemsSource, the items are displayed with .ToString().
How can I get my custom UserControl's ListBox items to display using the registered view?
You need to use ReactiveUI binding to get automatic view resolution not Xaml binding.

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;

Getting parent of new tab after adding to bound TabControl (mvvm)

I'm adding a close button to my tabs using the following guide:
http://www.codeproject.com/Articles/84213/How-to-add-a-Close-button-to-a-WPF-TabItem
This has become a problem because the event uses the 'parent' of the added tab to remove that tab from the tabcontrol. I'm binding the tab control using mvvm, so the parent property is apparently not being set and giving me a null reference exception for the parent when the event tries to remove from it.
Here's the binding so you get the idea:
<TabControl Name="tabControl" Margin="0,22,0.2,-5.2" ItemsSource="{Binding Tabs}" Background="#FF4C76B2"/>
Heres where the tabs are being added.
private void AddTab(object tabName)
{
ClosableTab newTab = new ClosableTab();
newTab.Title = "title?";
//newTab.Header = tabName;
TextBox test = new TextBox();
test.Text = "CONTENT (" + tabName + ") GOES HERE";
newTab.Content = test;
Tabs.Add(newTab);
OnPropertyChanged("Tabs");
}
Here is the event where the null reference is taking place:
void button_close_Click(object sender, RoutedEventArgs e)
{
((TabControl)this.Parent).Items.Remove(this);
}
As I see it there are two options:
try to find another way to remove the tab (without the parent
property)
try to find a way to somehow set the parent property (which cant be
done directly, it throws a compiler error)
That doesn't sound like MVVM to me. We work with data, not UI elements. We work with collections of classes that contain all of the properties required to fulfil some requirement and data bind those properties to the UI controls in DataTemplates. In this way, we add UI controls by adding data items into these collections and let the wonderful WPF templating system take care of the UI.
For example, you have a TabControl that we want to add or remove TabItems from... in a proper MVVM way. First, we need a collection of items that can represent each TabItem:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<string>), typeof(TestView));
public ObservableCollection<string> Items
{
get { return (ObservableCollection<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
I'm just using a DependencyProperty because I knocked this up in a UserControl and I'm just using a collection of strings for simplicity. You'll need to create a class that contains all of the data required for the whole TabItem content. Next, let's see the TabControl:
<TabControl ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ItemTemplate}" />
We data bind the collection to the TabControl.ItemsSource property and we set the TabControl.ItemTemplate to a Resource named ItemTemplate. Let's see that now:
xmlns:System="clr-namespace:System;assembly=mscorlib"
...
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type System:String}">
<TabItem Header="{Binding}" />
</DataTemplate>
This DataTemplate defines what each item in our collection will look like. For simplicity's sake, our strings are just data bound to the TabItem.Header property. This means that for each item we add into the collection, we'll now get a new TabItem with its Header property set to the value of the string:
Items.Add("Tab 1");
Items.Add("Tab 2");
Items.Add("Tab 3");
Note that I included the System XML Namespace Prefix for completeness, but you won't need that because your DataType will be your own custom class. You'll need more DataTemplates too. For example, if your custom class had a Header property and a Content property, which was another custom class, let's say called Content, that contained all of the properties for the TabItem.Content property, you could do this:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type YourPrefix:YourClass}">
<TabItem Header="{Binding Header}" Content="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:Content}">
<YourPrefix:SomeUserControl DataContext="{Binding}" />
</DataTemplate>
So this would give you TabItems with Headers set and Content that comes from SomeUserControl which you could design. You don't need to use UserControls, you could just add more UI controls to either DataTemplate. But you will need to add more controls somewhere... and more classes and properties, always remembering to correctly implement the essential INotifyPropertyChanged interface.
And finally, to answer your question in the proper MVVM way... to remove a TabItem, you simply remove the item that relates to that TabItem from the collection. Simple... or it would have been if you really had been using MVVM like you claim. It's really worth learning MVVM properly as you'll soon see the benefits. I'll leave you to find your own tutorials as there are many to chose from.
UPDATE >>>
Your event handling is still not so MVVM... you don't need to pass a reference of any view model anywhere. The MVVM way is to use commands in the view model. In particular, you should investigate the RelayCommand. I have my own version, but these commands enable us to perform actions from data bound Buttons and other UI controls using methods or inline delegates in the view model (where action and canExecute in this example are the CommandParameter values):
<Button Content="Close Tab" Command="{Binding CloseTabCommand}"
CommandParameter="{Binding}" />
...
public ICommand CloseTabCommand
{
get { return new ActionCommand(action => Items.Remove(action),
canExecute => canExecute != null && Items.Contains(canExecute)); }
}
So whatever view model has your Tabs collection should have an AddTabCommand and a CloseTabCommand that add and remove items from the Tabs collection. But just to be clear, for this to work properly, your ClosableTab class should be a data class and not a UI control class. Use a DataTemplate to specify it if it is a UI control.
You can find out about the RelayCommand from this article on MSDN.

Looking for guidance for where to put some code in a WPF MVVM application

First time attempting MVVM, looking for clarity on where to put some code.
My main view will need to bind to a list that will be holding 1 to many UserControls.
Would the List exist in the ViewModel or the Model? From what I'm reading, the model contains properties typically that the View binds to via the ViewModel. I don't see how that would work for this, the Model would not need to know about the List of UserControls which is a list of a View(UserControl), I may be making this harder than needed, but I'm still wrapping my mind around where code should be placed and I want to understand it. Thanks for any guidance, or if I did not explain myself well please let me know.
Your UserControls should have a ViewModel (Let's call it ItemViewModel by now).
Your MainViewModel should have an ObservableCollection<ItemViewModel>.
Then your view should have an ItemsControl (or one of its derivatives such as ListBox) for which the ItemsSource property will be bound to the ObservableCollection.
And then the ItemsControl should define an ItemTemplate that contains your UserControl.
This is the right way to do what you're describing with WPF / MVVM.
Example (pseudocode):
MainViewModel:
public class MainViewModel
{
public ObservableCollection<ItemViewModel> Items {get;set;}
}
MainView:
<Window>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<my:UserControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Keep in mind that each instance of UserControl will have it's DataContext set to the corresponding item in the source collection.
Therefore, if your ItemsViewModel looks something like this:
public class ItemsViewModel
{
public string LastName {get;set;}
public string FirstName {get;set;}
//INotifyPropertyChanged, etc.
}
your UserControl can be defined like this:
<UserControl>
<StackPanel>
<TextBox Text="{Binding LastName}"/>
<TextBox Text="{Binding FirstName}"/>
</StackPanel>
</UserControl>
You shouldn't need a list of UserControls. What you would likely have is your View binding to a List of items in your ViewModel. For example, create a ListBox and set it's ItemsSource to your ViewModel's list.
To create a your user control for each item, you would need to create a DataTemplate for the type in your list and specify your UserControl and you can give any bindings inside that usercontrol to the item.
The ListBox will then use the DataTemplate to create a UserControl for each item in the list.

Binding WPF combo box to specific dictionary instance

I am new to WPF and I am trying to get databinding to work with a combo box. I have a class I created called FolderList that basically wraps around a FileSystemWatcher instance. It has a property called Folders that returns a dictionary of the folder names and their full paths.
Then for the class for my WPF window it has contains an instance of FolderList called FolderWatcher that is configured in the constructor. I would like to databind a combobox to the dictionary in that specific instance of FolderList.
I've found examples where there are static instances of data objects or where they are created through XAML but I can't figure out how to bind to a specific instance.
I am not picking if this is done in XAML or C#. I've basically gotten this far with the ObjectDataProvider.
<Window.Resources>
<ObjectDataProvider x:Key="ProjectNames"
ObjectType="{x:Type local:FolderList}"
/>
</Window.Resources>
And here is the combo box I want to data bind. This doesn't produce any errors but it also isn't populated. I know enough to know I am missing something major in the ObjectDataProvider but I just don't know what it is.
<ComboBox Name="ProjectCombo" MinWidth="100" ItemsSource="{Binding Source={StaticResource ProjectNames}, Path=Folders}" />
You don't need a the ObjectDataProvider here. Just set ItemsSource of your ComboBox directly in the Window's contructor, where you initialize your instance of FolderList:
public MyWindow()
{
InitializeComponent();
FolderWatcher = new FolderList(...);
ProjectCombo.ItemsSource = FolderWatcher.Folders;
}
Another option would be to set the instance of FolderList as DataContext of your window and then use binding to set ItemsSource of theComboBox`:
public MyWindow()
{
InitializeComponent();
FolderWatcher = new FolderList(...);
DataContext = FolderWatcher;
}
<ComboBox Name="ProjectCombo" MinWidth="100" ItemsSource="{Binding Folders}" />
I suggest you look into MVVM pattern. If you designed your application according to that pattern, you would have a View Model instance as DataContext of your view and that View Model would expose a property you could bind to.

Categories

Resources