Question:
Is there a way to achieve what I'm looking for; to simply add a collection of views (UserControls) to a control whilst having a consistent MVVM structure? Is an ItemsControl the way to go, or should be have a different approach?
Problem
I have a collection of MVVM projects (with usercontrol mainviews) that I want to present in another control that can animate transitions between them. I have a horizontal listbox across the top of the control functioning as 'tabs' which are bound to the collection of views. The main body of the control shows a single view bound to the selected item of the list box and animates transitions when a new tab is selected.
My concern is having the collection of views as part of the new control's viewmodel doesn't make much sense in the MVVM paradigm and it would be more usable if the usercontrols could simply by added straight into the control almost as if it was a panel. This would lead me to think something like ItemsControl is the way to go and could be used like this:
<CustomItemsControl>
<UserControl1>
<UserControl2>
<UserControl3>
<UserControl4>
</CustomItemsControl>
Then have content that looked something like this.
<ItemsControl.Template>
<StackPanel>
<ListBox x:Name="Tabs" Height="35" SelectedIndex="0"
ItemsSource= >> The binding for the ItemsControls Collection <<
SelectionChanged="Tabs_TabSelected">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1"
Columns= >> Binding to Collection.Count <<
/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<TransitionCtrl Name="TransitionControl" CurrentView="{Binding SelectedItem, ElementName=Tabs}" />
</StackPanel>
</ItemsControl.Template>
From the examples I've looked at the ItemsControl isn't intended to be used in this way. Any suggestions to point me in the direction of what I'm trying to achieve?
You should be using PRISM view injection technique. The best approach would be using TabControl as a region and inject the main view when each module loaded. Since tab is not built as a region, you may need a custom region adapter as explained here.
<TabControl Grid.Row=”1″ Grid.Column=”1″
cal:RegionManager.RegionName=”TabRegion” Name=”TabRegion”>
From every module,
public void Initialize()
{
regionManager
.AddToRegion(“TabRegion”, new FirstView())
.AddToRegion(“TabRegion”, new SecondView());
}
Related
I am trying to find a best or common practice for creating a table of buttons from a database list.
For my use I am creating more of a Point of Sale type screen. Where I want the categories to load as buttons on the entry screen. The buttons would have a simple task of showing a screen of more dynamically created buttons of the actual items. Those buttons would add the items to, lets call it, a ticket.
The solutions I found were few. I am also trying to code this so others can pick it up fairly quickly. I am extremely rusty and only code once in a while. So I try to follow common ways of doing it. I had some ideas but the code becomes hard to read, mostly because of me.
I saw the below link but was not sure if a ListBox was a good container for this.
Dynamic filling WrapPanel buttons from DB, setting the event handlers
I am sure a wrappenel is what I would have to use, but do I put it in a container or use it directly. Do I put it in Xaml or code it all, for issues like spacing between buttons? I think I am overthinking it and need to skip for a little bit.
Thank you,
It sounds like you want an ItemsControl, bound to your categories, with a WrapPanel as the ItemsPanel. The Button would go in the ItemTemplate.
<ItemsControl ItemsSource="{Binding CategoriesFromDatabase}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"
Command="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl},Path=DataContext.AddToTicketCommand}"
CommandParameter="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here I assumed that your view model has properties "CategoriesFromDatabase" (an IEnumerable that you populate from the database), and an ICommand "AddtoTicketCommand" which takes the category as a parameter.
I'm having a Class Item : UserControl , I need to add these items in a Panel (like in Grid) multipletimes.
Note : I will clear and add those items very often.
On the performance and memory consumption basic, which method can i go for?
either, (i).just add those items (UserControl) like the below
for (int i = 0; i < myCollection.Count; i++)
{
Item _item = new Item();
_item.Name = "Item - "+i.ToString();
panel1.Children.Add(_item);
}
or
can i go with
<Grid ItemSource="myCollection" >
<ItemsControl >
<ItemsControl.ItemTemplate>
<DataTemplate>
//User control's template
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Thanks in advance.
Joy Oyiess Rex K
Regarding performance the worst thing you can do is create new instances of a UserControl over and over again. So basically what your solution (i) is suggesting.
Why? Because the xaml of a UserControl is always parsed for every instance you are creating.
So what can be done? You could design your control as a templated control. This way the template xaml is only parsed once, and reused whenever you instantiate an new instance.
But it would still be inadvisable to create new instances in your code. Silverlight has item controls that are able to handle large lists of entries and that can manage to only instantiate visual elements for entries that are actually visible.
So I strongly recommend using an ItemsControl with a virtualizing panel (only instantiating visual elements for visible entries).
<ItemsControl>
<ItemsControl.ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsControl.ItemsPanelTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<MyItemControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Some code for MyItemControl:
public class MyItemControl : Control
{
public MyItemControl(){DefaultStyleKey=typeof(MyItemControl);}
}
Some xaml for the control's default template:
<Style TargetType="MyItemControl">
<Setter Property="Template" Value="{StaticResource MyItemControlDefaultTemplate}"/>
</Style>
<ControlTemplate x:Key="MyItemControlDefaultTemplate" TargetType="MyItemControl">
...
</ControlTemplate>
I don't think there is a real difference.
I don't know about performance (i reckon XAML does the same as you under water), but memory consumption would be the same, since the control is created / garbage collected in both cases.
In a WPF application, I would like to display a grid of tiles (buttons essentially) using images from a folder location. There could be any different number of images in the folder, so the tiles/buttons need to be generated dynamically and formatted based on the amount. These need to be buttons that can trigger mouse click events.
I'm very new to C# and .NET, so I'd just like some direction on what the best way of doing this would be. I've started this as a WPF application so would using a template be a good idea? Or if just dynamically creating form buttons with background images is an easy option then I'll give that a go.
You would probably need a ItemsControl such as a ListView. Unfortunately WPF only ships the GridView implementation, but the ListView was intended to support all those views you see in the Windows file explorer. For a Tiled based view you would need to override the ViewBase class and assign it to the View property of the ListView.
Years ago I have had sample code that demonstrated what you want.
The following link contains MSDN samples:
MSDN ListView.View samples
How to: Create a Custom View Mode
I know that the Xceed DataGrid has a built-in CardView mode. I don't know if it is available in the free version: Xceed WPF DataGrid documentation
Edit I just checked the MSDN samples and I think they are close to what you want.
I would go for ItemsControl. You need a class representing your buttons, with properties such as X, Y, ImageUri, and so on. You expose your generated buttons via ObservableCollection and bind it to ItemsSource of your ItemsControl. Then you change your ItemsPanelTemplate to grid:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<!--Here go rows and columns definitions-->
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
If you have fixed number of rows and columns, you may add them directly in XAML, otherwise generate them at runtime in code-behind. You add ItemsContainerStyle for positioning:
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding Y}" />
<Setter Property="Grid.Column" Value="{Binding X}" />
</Style>
</ItemsControl.ItemContainerStyle>
Also, you need ItemTemplate which will cover displaying button with image. Other options would be to use Canvas or UniformGrid as ItemsPanelTemplate (both with their advantages and disadvantages).
I'm starting a new project in WPF and am now looking into using Prism. For now I'm simply trying to set up the navigation of the application using Prism. Unfortunately, my lack of experience with the framework makes it a bit difficult to get started.
To be more precise about my first challenge I have an application with a "navigation/menu" region and a "main" region.
In "navigation/menu" region, I have several checkboxes, in this case we have four of them, which represents a sequential navigation. I.E. we've selected View 2 and View 4.
So, when the user click Start, in "main" region must appear each view selected in that order. Check the below image, View 2 is first. Then when the user press next, must show View 4.
I mean on a more structural level..
if I could only get through the first steps..
Prism support TabControl Region Adapter, navigation can be done using standard requestNavigation method.
You need add all your tab content using Region.Add method to the region in your module's init phase.
view:
<TabControl prism:RegionManger.RegionName="tabRegion" />
C# code:
IRegionManager manager;
manager.Regions["tabRegion"].Views.Add(Container.Resolve(typeof(YourViewType)));
In your viewModel, you should write you navigation command:
public void NextView() {
regionManager.RequestNavigation("tabRegion", new Uri("YourViewType", UriKind.Relative));
}
bind to your "next" button:
<Button Command="{Binding NextViewCommand}" />
If you want to control whether user can navigate to next page, you can implement INavigationAware interface.
If you don't want lost data between navigation, you can make your view model has ContainerMangedLifeCycle or implement IsNavigationTarget method to return true.
Sorry for untested code sample, but you should get the point.
Create a class named ViewVM with a property IsSelected. Must implement INotifyPropertyChanged.
Add an ObservableCollection<View> named Views to your datacontext. Populate it with new instances of ViewVM.
Put an ItemsControl in your Window, with ItemsSource set to Views. The DataTemplate for the ItemsControl items should contain a CheckBox (with IsChecked bound to IsSelected) and a Label.
Add a TabControl to your Window, with ItemSource set to Views. Add a Style for TabItem such that TabItems are only visible if IsSelected is true.
Following the above steps will give you a window containing a list of views with checkboxes, as you requested, and a TabControl displaying only the selected views. Below is the XAML (I have tested this):
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=Views}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"></CheckBox>
<TextBlock Text="{Binding Path=Title}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TabControl ItemsSource="{Binding Path=Views}">
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Title}"></TextBlock>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Resources>
</TabControl>
</StackPanel>
This addresses the structural/design aspect and should give you a good start to creating your solution - you'll also need to create a custom control to use instead of the TabControl. Instead of having tabs, your custom control should contain Next and Previous buttons to navigate between views.
I'm wondering how to go about creating different views in the main window when a button is pressed. I'm not sure of the correct terminology, so that has hampered my google fu.
I'm thinking that the main viewing area would be a content control, that I could change when a event happens. I made a small drawing to help illustrate my idea / thought.
Any input will be appreciated. Thanks!
It would be really easy to implement this senario using MVVM approach....
Make a ViewModel for you MainView. Then Define Properties of the ViewModels of your UserControls
For Example You have Two UserControl as FirstView and SecondView then make a properties in your viewmodels as ViewToLoadProperty of the type ViewModel (usually called as ViewModelBase)
Set bindings as
<!-- Panel For Hosting UserControls -->
<Border Grid.Column="2">
<ContentControl Name="userControlContentControl"
Content="{Binding Path=ViewToLoadProperty,
}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type ViewModelLayer:FirstViewModel}">
<ViewLayer:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelLayer:SecondViewModel}">
<ViewLayer:SecondView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Border>
<!-- Panel For Hosting UserControls -->
Then when you click the button Use a command to set the respective ViewModel Intance to this(ViewToLoadProperty) property...(Use RelayCommannds or something like it)
DataTempates would do the rest of the job by selecting the right View according to the right type of ViewModel
YOu can use MVVMLight toolkit if you are implementing MVVM Pattern.. :)
On the right you could have a frame. Then the button would bind a different page or user control to the content of that frame.