WPF Single Selection Across Multiple ItemsControls - wpf

Part of my app has a month-view calendar interface, but I'm having trouble with item selection. The interface is set up so that each of the days in the view contains a ListBox of items, much like the month view in Outlook. The problem I'm experiencing is that I need to maintain a single item selection across all of the ListBoxes.
Below is a sample that should adequately describe my situation. I need to maintain a single selection between both ListBoxes.
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Numbers}"
SelectedItem="{Binding SelectedObject"/>
<ListBox Grid.Column="1" ItemsSource="{Binding Dates}"
SelectedItem="{Binding SelectedObject"/>
</Grid>
</Window>
And the view model for the window:
class MainWindowViewModel : DependencyObject
{
public static readonly DependencyProperty SelectedObjectProperty =
DependencyProperty.Register("SelectedObject", typeof(object),
typeof(MainWindowViewModel),
new UIPropertyMetadata(null));
public ObservableCollection<int> Numbers { get; set; }
public ObservableCollection<DateTime> Dates { get; set; }
public object SelectedObject
{
get { return GetValue(SelectedObjectProperty); }
set { SetValue(SelectedObjectProperty, value); }
}
}
In this primitive example, I would expect that when the SelectedObject property of my view model gets set to an item that's not in one ListBox, the selection would be removed in that ListBox, but that doesn't happen. I understand that I can simply name each ListBox, and hook into the SelectionChanged event. I'd prefer to not have to do that with an entire month-view calendar. There has to be a better way.
In a previous iteration of the app, I was able to create a SelectionManager static class with an attached property that was used to maintain selection. However, I can't use this now as the classes I'm using for my items are not DependencyObjects, and I'd really prefer not to have to create DependencyObject wrapper classes as this will considerably complicate my architecture.
Thanks.
EDIT 1: Added a view model class as requested.

Bind both ListBoxes to the same collection (make an observable collection of DatesAndNumber objects or something along those lines) and use converters and/or data templates to get the desired output. Then you can simply set IsSynchronizedWithCurrentItem to true on both ListBoxes to get the desired effect.
Reference: http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.selector.issynchronizedwithcurrentitem.aspx

how about using ICollectionView and its associated filter ?
here is a link which explains it better
http://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/

I figured it out by creating a separate selection manager class and an ISelectable interface. You can read about the details on CodeProject.

Related

Combo box with submenu

How can I show the combo box like this one, in C#.net 2010
It is possible by combining two controls ComboBox and TreeView in one UserControl.
Although this control looks quite simple, the actual implementation isn’t clear and takes long time.
Here is the sequence of steps:
1) Custom TreeView and TreeViewItem. They provide the following functionality:
Allow to expand and select an item from a view model (It isn’t possible to select an item of the TreeView using other ways)
The event which is fired when a user clicks a TreeViewItem (so it will be possible to close the ComboBox)
2) Interface for an item of the ItemsSource collection
public interface ITreeViewItemModel
{
string SelectedValuePath { get; }
string DisplayValuePath { get; }
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
IEnumerable<ITreeViewItemModel> GetHierarchy();
IEnumerable<ITreeViewItemModel> GetChildren();
}
Members of this interface:
IsExpanded – allows to expand the TreeViewItem from the bound view model. Must be implemented as the INotifyPropertyChanged property.
IsSelected – allows to select the TreeViewItem from the bound view model. Must be implemented as the INotifyPropertyChanged property.
SelectedValuePath – the unique string (unique item id) that will be used to select and expand the treeview control.
DisplayValuePath – the content that will be displayed at the header when the combobox is closed
GetHierarchy – returns the item and its parents.
GetChildren – returns child items of the current item
Create the Combobox:
It is the most difficult part of the implementation. I was forced to create many methods to provide the connection between Combobox and TreeView.
But although there is many private methods, there is only two public properties:
SelectedItem – now it is possible to get or set this item and it will be selected in the treeview.
SelectedHierarchy – it wasn’t necessary to create this property, but it wasn’t difficult so I’ve decided to implement it. Use list of strings instead of the actual item.
Add it to a view and bind with a view model:
<UserControl.Resources>
<Windows:HierarchicalDataTemplate x:Key="treeViewDataTemplate"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Title}" />
</Windows:HierarchicalDataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<local:ComboBoxTreeView ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
ItemTemplate="{StaticResource treeViewDataTemplate}"
HorizontalAlignment="Center" VerticalAlignment="Top" />
</Grid>
The ItemTemplate property is obligatory property and must be of the HierarchicalDataTemplate type.
Check this out https://vortexwolf.wordpress.com/2011/04/29/silverlight-combobox-with-treeview-inside/

WPF User control: can I bind properties from an internal and an external datacontext?

I am trying to build my own calendar control in WPF/XAML, both as an exercise and to be used in a hobby project. This calender will be a grid where each cell obviously represents a day in the chosen month. Each cell should be able to display a list of items. The inputs for the calendar should be an identification of a month and a list of days. Those "days" will be of a custom type, something like
public class CalendarDay
{
public int DayNumber {get; set;}
public List<DayItem> Items {get; set;}
}
where a DayItem could represent something like an appointment or a todo.
I'm implementing this as a user control. The XAML for this calendar is a ControlTemplate that contains a UniformGrid of 1x7 for the day names (data bound to a collection 7 strings) and a UniformGrid of 6x7 for the days (data bound to a collection of CalendarDay).
A view (user control) that contains this calendar conceptually looks like this:
<UserControl name="myView" ... xmlns:cal="clr-namespace:the calendar namespace">
<Grid>
<cal:Calendar Days="{Binding DaysWithItems}" CurrentMonth="{Binding DisplayMonth}" />
</Grid>
</UserControl>
As I'm applying MVVM, myView will have a DataContext that is set to some view model class that has a property DaysWithItems (a list of CalenderDay instances) and a property DisplayMonth.
Ideally, the consumer of this calendar control should only have to provide the two inputs as mentioned. Moreover, DaysWithItems should, from myView's point of view (pun is coincidental), be a list of 28, 29, 30 or 31 elements, depending on the month. This means that the list should somehow be padded to 42 items. I think this should be the responsibility of the calendar control, not myView's view model.
Note that I'm also not provding the day names. This too should be the responsibility of the calendar control. This shouldn't be provided explicitly.
Here's my problem. If, in the calendar's control template, I want to bind to the string collection for the day names and the 42 element collection of CalendarDay, the datacontext should be the Calendar class itself (because of the responsibilities I explained earlier).
On the other hand, in myView, I'm binding the calendar to myView's DaysWithItems (the (logical) collection that contains 28..31 elements), so there the calendar's datacontext should be myView's view model.
Can I use some sort of internal datacontext (= "internal" to the control template) and also some sort of external datacontext (= a datacontext as provided by the consumer of the calendar control)?
The easiest way to bind to something other than your DataContext is by using ElementName
You can do the following:
Example for UserControl.xaml.cs:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int), typeof(UserControl1), new PropertyMetadata(0));
}
UserControl.Xaml
<UserControl x:Class="GridSplitterTest.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" x:Name="MyControl"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Content="{Binding ElementName=MyControl, Path=MyProperty}"></Button>
</Grid>
</UserControl>
By using ElementName and pointing to the UserControl you are bypassing the DataContext and able to bind to properties inside your UC.
Here's the down-low on these kind of data bindings. When you want to data bind to a property from an object that is set as the DataContext, you use a normal Binding Path:
<TextBlock Text="{Binding PropertyOfDataContextObject}" />
However, when you also want to data bind to a property (from inside a UserControl that is declared in that UserControl (or Window)) you should use a RelativeSource Binding:
<TextBlock Text="{Binding PropertyOfUserControl, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourUserControl}}}" />
You can alternatively name a control and use the Binding.ElementName Property to reference it:
<TextBlock Text="{Binding PropertyOfNamedControl, ElementName=NameOfControl}" />

MVVM - Binding Lists on a Single Page / How to setup MainView and ItemView Models and setup Databinding

MVVM Question for Windows Phone
Suppose you want to have multiple Lists in your View Model and have say a Pivot Form with a different ListBox in each Panel. For illustration purposes lets say we have two listboxes for People and Places in a single page but on different panels. (PeopleList and PlacesList)
How do you setup your ViewModels. Is there one MainViewModel for each list? One MainViewModel with two Lists? I'd like to be able to navigate to the appropriate detail page detail page based on their selection.
Secondly, how do you bind each listbox to a different viewmodel when the form is loaded.
My confusion is that examples seem to indicate that when a form is loaded you set context to a single "static variable" and not sure how to specify a different source of each listbox.
Below is some sample code snippets... with questions ???
DataContext = App.ViewModel ;
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
this.Items = new ObservableCollection<ItemViewModel>();
//?? can you have more than one of these?
}
//?? should I have Public MainViewModel2() with this.Items = new OC<IVM2>
//...
/// Creates and adds a few ItemViewModel objects into the Items collection.
/// </summary>
public void LoadData()
{
this.Items.Add(new ItemViewModel() { VAR1 = "X", VAR2 = "Y"}) ;
<Grid x:Name="LayoutRoot" Background="Transparent" >
<!--Pivot Control-->
<controls:Pivot x:Name="Pivot" Title="MyApp" DataContext="{Binding}" Loaded="Pivot_Loaded">
<!--Pivot item one-->
...
<!--Pivot item two-->
<controls:PivotItem Header="people">
<Grid>
<ListBox x:Name="PeopleList" Height="442" HorizontalAlignment="Left" Margin="46,68,0,0" VerticalAlignment="Top" Width="346" ItemsSource="{Binding ItemsA}" SelectionChanged="ListBox1_SelectionChanged" />
</Grid>
<!--Pivot item three-->
<controls:PivotItem Header="places">
<Grid>
<ListBox x:Name="PlacesList" Height="442" HorizontalAlignment="Left" Margin="46,68,0,0" VerticalAlignment="Top" Width="346" ItemsSource="{Binding ItemsB}" SelectionChanged="ListBox2_SelectionChanged" />
</Grid>
Answers to some of the questions:
How many / which ViewModels?
You should certainly have a MainViewModel here. And then that MainViewModel has
two properties of the types PeopleListViewModel and PlacesListViewModel, or
two list-properties ObservableCollection<PersonViewModel> and ObservableCollection<PlaceViewModel>. Yes, you can have as many as you like but calling them ItemsA and ItemsB isn't the best choice.
In the first option, create 2 views (UserControls) to hold the lists.
In the second option, you can use an ItemsControl and a DataTemplate to show the lists.
And in general, in MVVM try to avoid SelectedItemChanged and other events. You can databind (a section of the View) to a SelectedItem property.

MVVM WPF design related query : use of UserControls

I have one query related to designing WPF using MVVM
Here is the scenario :
1> I have one WPF screen which contains various user controls which are reusable in some other screens too.
2> Can i have separate ViewModel class for each of those user controls , what could be ideal design in this scenario
3> Should i separate my Viewmodel based on individual screen or on UserControls .
4> If i create separate viewmodels based on UserControls how i should integrate it .
Is there any design guidelines around this !!
Urgent Help appreciated ..
This post describes what I do in certain scenario, I don't know if it is a best practice or not but it works for me.
I create ViewModel for my Window that holds all the user controls, so this called ContainerViewModel and I create an instance of that Viewmodel and put it in the DataContext of the Window. From that moment all the UserControls can access that ViewModel with Binding.
The next thing to do is to create a property on my ContainerViewModel for everty UserControl that holds the ViewModel for each UserControl.
Then use binding to attach the usercontrols ViewModel to the DataContext property of the Usercontrol.
example of the viewmodels and a window with 2 listboxes instead of usercontrols:
Viewmodel classes without any implementation but just empty classes to show the concept:
public class ContainerViewModel
{
public ContainerViewModel()
{
ViewModelForControl1 = new Control1ViewModel();
ViewModelForControl2 = new Control2ViewModel();
}
public Control1ViewModel ViewModelForControl1 { get; set; }
public Control2ViewModel ViewModelForControl2 { get; set; }
}
public class Control1ViewModel { }
public class Control2ViewModel { }
Window xaml:
<Window x:Class="ConfigHellp.UI.Windows.ContainerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:ConfigHellp.UI.ViewModel"
mc:Ignorable="d"
DataContext="{DynamicResource ContainerViewModel}" >
<Window.Resources>
<vm:ContainerViewModel x:Key="ContainerViewModel" d:IsDataSource="True" />
</Window.Resources>
<StackPanel>
<ListBox DataContext="{Binding ViewModelForControl1}" />
<ListBox DataContext="{Binding ViewModelForControl2}" />
</StackPanel>
</Window>
this depends on how complex the embedding of the UserControl into the environment is. If you think that its to much effort to build the view model logic for your user control again and again (which is also a very nice source for mistakes), you should infact encapsulate the logic in a single viewmodel for your control. If the user control will be an ListItem for example, i generally suggest you to build an own viewmodel for the control.
The infrastructure will be than:
A general viewmodel for your WPF screen, which holds instances of the viewmodels for your usercontrols. That DataContext of the screen will be the general viewmodel. The users controls's DataContext will be a Binding to the PropertyPath of the user control viewmodel in your general viewmodel. e.g:
In WPF Screen:
<ListBox DataContext="{Binding}" ItemsSource="{Binding Path=ItemList}">
<ListBox.ItemTemplate>
<yourControls:YourUserControl />
</ListBox.ItemTemplate>
</ListBox>
In the general viewmodel:
public class ScreenViewModel : INotifyPropertyChanged
{
private ObservableCollection<YourUserControlViewModel> _itemList =
new ObservableCollection<YourUserControlViewModel>();
public ObservableCollection<YourUserControlViewModel> ItemList
{
get { return _itemList; }
set { _itemList = value; }
}
}
This will automatically generate a your user control for each viewmodel in the ItemList of your general view model.

WPF MenuItem Content "Name"

I have a LOT of MenuItem(s), and I want to be able to change their "Content" so that it displays in the program. When I load up the program, their "Content Name" is set in a Setter I created.. but the only problem is that I have almost a hundred MenuItem objects, and I need their display names in the program to be different (not the setter's default). I could just create over 100 different "Setter"'s and change one line in them.. but that is very time consuming. Is there a simpler approach? I want to be able to do this in the XAML where I am declaring them. Is there a way to do this? I've been searching and trying different attempts, but nothing so far.. perhaps someone knows?
EDIT:
Sorry, Perhaps I am being a bit unclear..
I already have created the MenuItems and they are based on the Setter that I have created... The problem is.. I now want each one to still be based on that Setter, but to have a unique "Content"/Name that displays for the user...Currently, they all have the "Content" name given to them by the setter, but I am looking for a way to set each MenuItem's content name through XAML.. is this possible?
Thanks
You question is not clear. i think the best way to create hundreds of menu items is to create them from the code not in XAML. for example in a foreach loop. then you can give each of them a unique and meaningfull name. please describe your problem more clearly.
thanks
Now I understand your problem. generaly i think it would be a very bad idea to set the content property for each of your menuItems in the XAML file. Specialy when you are dealing with hundreds of items. a better way is to use the Data binding feature of WPF and DataTemplates, not to hardcode the menuItem names in the XAML file. I will propuse two solutions for your problem. first solution uses the code-behind approach to create menu items and then bind them to MainMenu's ItemsSource property without using dataTemplates. the following code is the code-behind code for the window:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MenuItems = new ObservableCollection<MenuItem>();
for (int i = 0; i < 40; i++)
{
MenuItem menuItem = new MenuItem();
menuItem.Header = "MenuItem" + i.ToString();
MenuItems.Add(menuItem);
}
MainMenu.DataContext = this;
}
public ObservableCollection<MenuItem> MenuItems
{
get;
set;
}
}
in this code first we created 40 number of menuItems and then we bind them to the DataContext property of the MainMenu object. the following code shows the XAML code of the windows including it's MainMenu object:
<Window x:Class="WpfApplication17.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Menu Grid.Row="0" Name="MainMenu" ItemsSource="{Binding MenuItems}">
</Menu>
</Grid>
</Window>
in this approch you can first create all of your menu items and their names in the code and after that bind them to the Menu object. then you can use styles to set common properties of the menu items.
but a better solution is to use dataTemplates as I did in the following code. in this approach first you created a class to store your menu item names. then with the help of the data template feature of WPF you can bind them to your MainMenu items. the code-behind of this solution is as follows:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MenuItems = new ObservableCollection<CustomMenuItem>();
MenuItems.Add(new CustomMenuItem("Item 1"));
MenuItems.Add(new CustomMenuItem("Item 2"));
MenuItems.Add(new CustomMenuItem("Item 3"));
MainMenu.DataContext = this;
}
public ObservableCollection<CustomMenuItem> MenuItems
{
get;
set;
}
}
public class CustomMenuItem
{
public string Name { get; set; }
public CustomMenuItem(string name)
{
Name = name;
}
}
in this code I used the CustomMenuItem class to store menuitem names. the MainWindows constructor is respossible for creating the menuitems but you can retrieve them from other sources, like a XML file of database. the XAML code for the MainWindow is like this:
<Window x:Class="WpfApplication17.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication17"
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<DataTemplate DataType="{x:Type local:CustomMenuItem}">
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Menu Grid.Row="0" Name="MainMenu" ItemsSource="{Binding MenuItems}">
</Menu>
</Grid>
</Window>
this way you can retrieve your menuitem names fot\r example from a XML file or from other data sources and they are not hardcoded into you XAML file. then you can use the powerfull features of DataTemplates to view you menu items the way you like.

Resources