Why would ItemsControl show a view but ContentControl show nothing? - wpf

I've got an application based on Prism.
This is my shell:
<Window x:Class="AvarioCRM3.ShellV2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.codeplex.com/CompositeWPF" >
<DockPanel LastChildFill="True">
<Border
Padding="10"
DockPanel.Dock="Top"
Background="#ddd">
<DockPanel>
<ItemsControl
Name="MainNavigationPanel"
cal:RegionManager.RegionName="MainNavigationPanel"
DockPanel.Dock="Top"/>
</DockPanel>
</Border>
</DockPanel>
</Window>
In my MenuModule I add a view to the region and it shows fine:
public void Initialize()
{
MainNavigationPresenter mainNavigationPresenter = this.container.Resolve<MainNavigationPresenter>();
IRegion mainRegion = this.regionManager.Regions["MainNavigationPanel"];
mainRegion.Add(new TestView());
}
The problem is: I don't want an ItemsControl in my shell, I want a ContentControl, but when I use a ContentControl, it shows nothing.
Why would ItemsControl show my views and ContentControl show nothing?

Could this be because a ContentControl will only display a single child, whereas an ItemsControl has multiple children?
I have't worked with Prism, but the API suggests that an IRegion is expected to have multiple children. If you're using a ContentControl then it is a little ambiguous what happens when I do the following:
IRegion mainRegion = this.regionManager.Regions["MainNavigationPanel"];
mainRegion.Add(new TestView());
mainRegion.Add(new SecondTestView());

Unlike the ItemsControl with a ContentControl you also need to activate the view once you have added it to make it visible.
MainNavigationPresenter mainNavigationPresenter = this.container.Resolve<MainNavigationPresenter>();
IRegion mainRegion = this.regionManager.Regions["MainNavigationPanel"];
TestView view = new TestView()
mainRegion.Add(view);
mainRegion.Activate(view);

I noticed you are doing this in Initialize. Could be too early? Have you tried using registration rather than injection of your view to see if that changed anything?
regionManager.RegisterViewWithRegion("MainNavigationPanel", typeof(TestView));
This won't solve your problem, however it will prove that the problem is trying to add something before your region is actually available. RegisterViewWithRegion will delay the creation and display of the view until the region is available.

Related

Showing UserControl once at the time with DataTemplateSelector

I have a couple specific user controls to Show some Content, e.g. simple like Image, WebControl but also two complex specific custom controls drawing on a canvas.
Now I thought using the DataTemplateSelector to handle the different UserControls. I actully used this http://tech.pro/tutorial/807/wpf-tutorial-how-to-use-a-datatemplateselector as a reference.
I changed the code so the form loads the UserControls dynamically (according to the file extension) in the following collection:
ObservableCollection<string> _pathCollection = new ObservableCollection<string>();
The only difference to the reference is now I want to navigate back and forward to the next control by showing one control only at the time. Which control should I use instead of ListView?
<Grid>
<ListView ScrollViewer.CanContentScroll="False"
ItemsSource="{Binding ElementName=This, Path=PathCollection}"
ItemTemplateSelector="{StaticResource imgStringTemplateSelector}">
</ListView>
</Grid>
How do I need to bind it to the template (equal to ItemTemplateSelector above)? WPF is still very new to me and I am learning.
Use a ContentControl. Bind your current item to the Content-property and the DataTemplateSelector to the ContentTemplateSelector-property.
<ContentControl Content="{Binding Path=CurrentItem, Mode=OneWay}", ContentTemplateSelector="{StaticResource imgStringTemplateSelector}" />
Your CurrentItem should be a DependencyProperty or a INotifyPropertyChanged-property of your DataContext. When you change your CurrentItem, the ContentControl will update the template automatically with help of your TemplateSelector.

WPF: How to create events for custom usercontrols?

I have created a UserControl that consists of an ItemsCollection. Each item in this collection consists of a ListBox.
My application is represented by a Window, which contains this UserControl. I want to be able to manage events related to items inside the ListBox. How can I achieve this? (I am not sure if this is relevant or not, but the UserControl is in a assembly different from the application.)
Here's the code of the UserControl:
<UserControl
x:Class="UserControls.CalendarMonthViewControl.CalendarMonthView"
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"
d:DesignWidth="580"
d:DesignHeight="300"
xmlns:calendarMonthViewControl="clr-namespace:UserControls.CalendarMonthViewControl"
Name="CalendarMonthViewControl">
<Grid>
<ItemsControl
ItemsSource="{Binding ElementName=CalendarMonthViewControl, Path=Days}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="6" Columns="7" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type calendarMonthViewControl:Day}">
<Border>
<Grid>
<ListBox ItemsSource="{Binding Path=CalendarDayItems}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
#vlad is on the right track.
There's a couple of options here. Both of them involve handling routed events.
To handle a routed event, you use the name of the owning class followed by the name of the event.
The first option is to simply handle the selection changed events (or some other ListBox event) on the Window class:
<Window ...
ListBox.SelectionChanged="OnChildListboxSelectionChanged">
...
</Window>
The second option (more typical approach) is to handle the ListBox events inside of the UserControl and then aggregate them in some way and fire an event at that level. This event is then handled by the Window. This event could be a routed event or a standard .NET event.
<UserControl ...
ListBox.SelectionChanged="OnChildListBoxSelectionChanged">
...
</UserControl>
Code behind for user control:
public event EventHandler<MyArgTypeEventArgs> ListBoxUpdated;
private void OnChildListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
// aggregate data and info
MyArgTypeEventArgs handler = ListBoxUpdated;
if (handler != null)
handler.Invoke(this, GenerateArgs());
}
The Window handles this event:
<Window ...
ListBoxUpdated="OnListBoxUpdated">
...
</Window>
This should give you something to start with.
I haven't used them much myself, but I think RoutedEvents would solve your problem. The events bubble up from your ListBox to the Window (or another element lower in the tree) where you can handle them.
edit: quoting from the link: To add a handler for an event using XAML, you declare the event name as an attribute on the element that is an event listener. The value of the attribute is the name of your implemented handler method, which must exist in the partial class of the code-behind file.
since UserControl inherits from UIElement, I'm guessing something like this would work (untested atm):
<UserControl
x:Class="UserControls.CalendarMonthViewControl.CalendarMonthView"
[...]
ListBox.NameOfEvent="EventHandlerName">
I don't think what you're trying to do is the right approach as it's creating unnecessary dependence between your views and means that your UserControl is not properly encapsulated.
In my opinion the nice solution would be to handle events in your UserControl's viewmodel and set up a relation between the viewmodel of your Window as needed, so that the views themselves are independent.

MVVM way to use different controls for editing different objects

I need to design a form with a treeview in the left and a container for some other control in the remaining area. Whenever user selects an item in the treeview some custom control appears on the right. For example, let's say that the treeview contains values "Audio settings" and "Video settings", and I have two controls that can be bound to these settings and I want to display them on the form when needed.
Now, from what I've read about MVVM, I shouldn't have properties that will return UserControls or DataTemplates, am I right? It will be messing with "VM shouldn't know implementation details of the view" as I see it. So, how do I handle this situation properly in terms of MVVM? Should I maybe use converters for this and if so, how would it look?
I can't provide any code at the moment (mostly because there isn't any), but I will try to clarify the problem if needed.
Thanks in advance.
This is where the WPF templating system helps out.
The main idea is to have a ContentControl display the appropriate view depending on the selected value in the TreeView.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<ListBox DockPanel.Dock="Left" ItemsSource="{Binding Editors}" SelectedItem="{Binding SelectedEditor}" />
<ContentControl Content="{Binding SelectedEditor}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type l:VideoViewModel}">
<l:VideoView />
</DataTemplate>
<DataTemplate DataType="{x:Type l:AudioViewModel}">
<l:AudioView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</Window>
AudioView and VideoView are UserControls.
As you can see, the ContentControl's content is bound to the SelectedEditor property in the ViewModel, which is also bound to the SelectedItem of the Listbox.
So the ViewModel for the main view looks like this.
public class MainWindowViewModel : INotifyPropertyChanged
{
public IEnumerable<object> Editors
{
get
{
yield return new VideoViewModel();
yield return new AudioViewModel();
}
}
private object selectedEditor;
public object SelectedEditor
{
get { return selectedEditor; }
set
{
if (selectedEditor == value)
return;
selectedEditor = value;
OnPropertyChanged("SelectedEditor");
}
}
}
So you can see that the main ViewModel has no GUI data in it.
To handle hooking up a TreeView to a SelectedItem property in a ViewModel see Data binding to SelectedItem in a WPF Treeview
You can implement it throw two properties: ShowAudioSettings and ShowVideoSettings in ViewModel and bind it on Visibility of your controls.
Also, you can make it with VisualStateManager.

WPF: Composite Application with Page Navigation

I am currently writing an application to which the composite methodology fits like a glove.... almost!
I also need a way to navigate between views, including maintaining a journal for navigation backward and forward.
What is the best way to combine these two methodologies, on one hand the single Window based CAG shell with its UserControl derived views, and on the other hand the convenient NavigationWindow shell with its Page derived views and journal?
Thanks!
You can display anything in a NavigationWindow, not just Pages. A simple way to make it work is to define in the NavigationWindow's resources a DataTemplate for each ViewModel you want to display. Bind the Content property of the NavigationWindow to a property of your main ViewModel, and you're done : changing that property will update the NavigationWindow content, and the appropriate DataTemplate will be picked automatically
UPDATE
I just looked at the code of a project of mine where I used a NavigationWindow. Actually I was mistaken, it doesn't work by binding the Content (or maybe it works, but that's not what I did). Instead I created a INavigationService interface, implemented by my App class, which handles the navigation by calling the NavigationWindow.Navigate method. That way, the navigation history is maintained by the NavigationWindow.
Here's an extract from my project
MainWindow.xaml :
<NavigationWindow x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:MyApp.ViewModel"
xmlns:view="clr-namespace:MyApp.View"
Title="{Binding Content.DisplayName, RelativeSource={RelativeSource Self}, FallbackValue=The Title}"
Height="600" Width="800">
<NavigationWindow.Resources>
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<view:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
<view:CustomerView />
</DataTemplate>
</NavigationWindow.Resources>
</NavigationWindow>
App.xaml.cs :
...
private void Application_Startup(object sender, StartupEventArgs e)
{
LoadConfig();
MyApp.MainWindow window = new MainWindow();
INavigationService navigationService = this;
HomeViewModel viewModel = new HomeViewModel(navigationService);
this.MainWindow = window;
window.Navigate(viewModel);
window.Show();
}
When I need to navigate to another view, I just call the Navigate method with the ViewModel as a parameter, and WPF automatically picks the appropriate DataTemplate from the resources.

Silverlight: Nested containers and Binding

The Scenario Bit:
On one of the controls within my Silverlight application I have embedded a custom user control. Within this embedded control is another custom user control that contains a datagrid. I would like to use binding to populate the datagrid. Easy enough I just sepcificy a collection that is in the DataContext of the parent control.
Parent Form:
<UserControl x:Class="ParentControl"
...>
<Grid x:Name="LayoutRoot" >
<ReusableControl />
</Grid>
</UserControl>
Parent Codebehind:
public partial class ParentControl: UserControl
{
public ParentControl()
{
InitializeComponent();
this.DataContext = ObjectCollection;
}
public ObservableCollection<object> ObjectCollection
{
get ;
set ;
}
}
Intermediate Form
<UserControl x:Class="ReusableControl"
...>
<Grid x:Name="LayoutRoot" Background="Gold">
<CustomDataGrid />
</Grid>
</UserControl>
Child Control:
<UserControl x:Class="CustomDataGrid"
...>
<Grid x:Name="LayoutRoot">
<data:DataGrid x:Name="dgItems"
AutoGenerateColumns="True"
ItemsSource="{Binding ObjectCollection}"
>
</data:DataGrid>
</Grid>
</UserControl>
The Question Bit:
I want to specificy the columns of the datagrid dynamically, based on another collection in the parent control DataContext. How can I do this? Is there more than one way of skinning this cat?*
Thanks,
Mark
*No cats where harmed during the asking of this question.
After many hours I have found a work-around which I have posted here. This doesn't strike me as the best solution in the world, but it works, and doesn't need the registration of event handlers throughout the application. Also it works top down, which is what I wanted.
I suspect that I could use Dependency Properties a little better, to prevent the need for DP's and NP's in the same class, but I'm out of time :-(
Hope this helps someone else.

Resources