ActivateItem not working in Caliburn.Micro - wpf

I am using Caliburn Micro for MVVM. In my MainView (shell), I have two controls. One hosts a RibbonView and another ContentControl which loads contents depending on the RibbonView menu selection. Here is the MainView (shell)
MainView (shell)
<Window x:Class="HotelReservation.Main.Views.MainView">
<DockPanel>
<ContentControl x:Name="RibbonView" DockPanel.Dock="Top"/>
<Grid DockPanel.Dock="Bottom" VerticalAlignment="Stretch" >
<ContentControl x:Name="ActiveItem"/>
</Grid>
</DockPanel>
</Window>
RibbonView
<Ribbon Margin="0,-20,0,0">
<RibbonTab Header="Room Band">
<RibbonGroup>
<RibbonButton Label="List" x:Name="RoomBandMain"
LargeImageSource="/HotelReservation.Global;component/Images/room-band-list-icon.png">
</RibbonButton>
</RibbonGroup>
</RibbonTab>
</Ribbon>
RibbonViewModel
public class RibbonViewModel : Conductor<object> {
public void RoomBandMain() { //Load in ActiveItem of MainView
ActivateItem(container.GetExportedValue<RoomBandMainViewModel>());
}
}
As can be seen, I am trying to load RoomBandMainViewModel in the <ContentControl x:Name="ActiveItem"/> The issue is that it is not loaded and I get a blank screen even though ActivateItem(container.GetExportedValue<RoomBandMainViewModel>()) code runs. I think that the <ContentControl x:Name="ActiveItem"/> exists not in RibbonView but its parent MainView, and hence the ActivateItem doesn't work.
How to resolve this issue.
Edit:
I had to set the DataContext of the <ContentControl x:Name="ActiveItem"/> to RibbonViewModel, so that ActiveItem is now property of RibbonViewModel and not MainViewModel. MainViewModel looks like below
So the MainView (shell) is now as follows
<Window x:Class="Conductor_Main.Views.MainView">
<DockPanel>
<ContentControl x:Name="RibbonView" DockPanel.Dock="Top"/>
<Grid DockPanel.Dock="Bottom" VerticalAlignment="Stretch" Background="Green"
DataContext="{Binding RibbonView}">
<ContentControl x:Name="ActiveItem" />
</Grid>
</DockPanel>
</Window>
Now the <ContentControl x:Name="ActiveItem" /> actually belongs to the RibbonViewModel.

What you have here is some kind of lifecycle of your windows. This has to be handled by the parent window of your ActiveItem.
The way to get this done the caliburn.micro way is to have a Conductor above the ActiveItem. In your case this is the MainWindow.
Your RibbonViewModel can be a Conductor, too. But only for it's own children. There can be more than one conductor.
From the caliburn documentation
Once you introduce the notion of a Screen Activation Lifecycle into
your application, you need some way to enforce it. This is the role of
the ScreenConductor. When you show a screen, the conductor makes sure
it is properly activated.
Which is quite a direct way of saying: If you have activation / life cycle, then use a conductor.

Related

Accessing multiple view models

I have 2 simple user controls and 2 view models that bind to them. I now want to add these to a master user control but want to know the correct way of accessing ViewModel properties from View 1 in my View 2 in the text block named FromView1?
My View 1:
<UserControl x:Class="v1"
.....
<Grid>
<TextBlock Text="{Binding Text1}" />
</Grid>
</UserControl>
My View 2:
<UserControl x:Class="v2"
..
<Grid>
<StackPanel>
<TextBlock Text="{Binding Text2}" />
<TextBlock x:name="FromView1" Text="{Binding Text1}" Background="Red" Width="100" />
</StackPanel>
</Grid>
</UserControl>
My main view containing View 1 and View 2
<UserControl x:Class="vMain"
....
<Grid Margin="20">
<StackPanel>
<local:v1 x:Name="v1"/>
<local:v2 x:Name="v2"/>
</StackPanel>
</Grid>
</UserControl>
Instead of having two separate view models, you should merge them into one single MainViewModel. v1 and v2 would should then share the DataContext with the vMain view which means they can bind to whatever property they want do.
So simply set the DataContext of vMain to an instance of MainViewModel and make sure that you don't set the DataContext of v1 or v2 explicitly somewhere.
UserControls should generally speaking not have their own view models. They should inherit the DataContext from their parent, which is usually a window.
If your view models tend to get big, you could use partial classes to split the definitions across several source code files.
your mainView's Viewmodel need to have ViewModel1 and ViewModel2
public class MainViewModel
{
....
public ViewModel1 vm1 {get;set;}
pulbic ViewModel2 vm2 {get;set;}
....
}
Xaml is ..
<UserControl x:Class="vMain"
....
<Grid Margin="20">
<StackPanel>
<local:v1 x:Name="v1" DataContext="{Binding vm1}"/>
<local:v2 x:Name="v2" DataContext="{Binding vm2}"/>
</StackPanel>
</Grid>
</UserControl>

display content class xaml vb.net

Well i have a mainWindow written in xaml (vb.net) with 10 buttons and for each button i have a class (with buttons...) in my project. Example: when i click on button valid i display the content of the class valid in my grid. Each class is instantiated in the class mainWdow.
I also have a grid on this mainWindow.
When i click on a button in my mainWindow, i would like to display the content of the correspondig class in my grid and of course i want to be able to use this class (click on buttons...).
What's the best way to do this on vb.net / xaml please?
On this webpage http://msdn.microsoft.com/en-us/library/cc903947(v=vs.95).aspx there are some indications but it is not what i want because i don't want just only display the content on my class but i want to be able to work with the class displayed on my grid...
I give you thanks for your advices.
Ok, so first you should read up about DataTemplates from the Data Templating Overview page on MSDN. Now here is the basic principal... instead of putting your reusable XAML into Windows, declare them in DataTemplates in your Window.Resources section:
<DataTemplate x:Key="StopDataTemplate">
<!--Define your content here-->
</DataTemplate>
<DataTemplate x:Key="ValidDataTemplate">
<!--Define your content here-->
</DataTemplate>
Now you can display the content of these DataTemplates in a ContentControl:
<ContentControl Name="Content" ContentTemplate="{StaticResource ValidDataTemplate}" />
Then when you want to switch to the other view, you just need to set the ContentControl.ContentTemplate property to the other DataTemplate (from Window code behind):
DataTemplate dataTemplate = (DataTemplate)FindResource("StopDataTemplate");
Content.ContentTemplate = dataTemplate;
Well, here is what i have done to do some tests:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate x:Key="CBTemplate">
<Grid Height="200" HorizontalAlignment="Left" Name="centralGrid" VerticalAlignment="Top" Width="200" Background="Red">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{Binding Title}"
Margin="10" HorizontalAlignment="Left" FontSize="20"/>
</Grid>
</DataTemplate>
then in my mainWindow.xaml i have this:
<Grid x:Name="layout" Background="LightBlue" Margin="250,150,260,380">
<ContentControl Name="Content" ContentTemplate="{StaticResource CBTemplate}" />
</Grid>
And in the mainWindow.vb i have:
Class MainWindow
Private t As Thing
Sub New()
InitializeComponent()
t = New Thing("test")
layout.DataContext = t
End Sub
End Class
So my program compiles properly but there is a little problem: text="{Binding Title}" doesn't work and when i replace it with "toto", there is no problem toto is displayed...
Did i miss something in my code?

Need a toggle menu that closes when it loses focus

This is going to be difficult to explain but bear with me.
I'm creating an application in WPF using the MVVM pattern. I'm new to it which is why I'm asking this question.
I have the application set up as 3 pages or views within a window. One of these is static and is always there, the other two are a couple of small settings pages that open in the top corner over the top of everything using zindex. At the moment the menu that opens these pages uses a listbox with togglebuttons as it's template (the checked state is bound to the listbox) so that you can click to open the menu, then click the button again to close it.
In an ideal world I'd like it so that if the menu page were to lose focus (listen for a click on the static view?) the settings views close too. Also I wondered if anyone had a simpler solution for a menu that works in a similar way because at the moment it is a pretty messy solution. Here are some code samples:
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding PageViewModels}" SelectedItem="{Binding CurrentPageViewModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Margin="10,0" Text="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FCCC"/>
<ToggleButton
VerticalAlignment="Stretch"
Content=""
IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Settings views -->
<ContentControl Panel.ZIndex="2" Grid.Row="1" Grid.Column="0" Content="{Binding CurrentPageViewModel}"/>
<!-- Main page view -->
<ContentControl Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="2" Width="1000" Height="700" Content="{Binding StaticPageViewModel}"/>
I'm using the concepts in this blog post to manage my views and viewmodels, however I changed the way the menu is shown so I could remove the need for a change page command/ICommand.
TL;DR : I'm looking for suggestions and criticism with what I could do to improve the way I've currently created my menu bar.
I would create an attached property if you really wanna do the MVVM style to close the View when it loses focus.
public static readonly DependencyProperty CloseViewOnLostFocusProperty =
DependencyProperty.RegisterAttached("CloseViewOnLostFocus", typeof (object), typeof (MainWindow), new FrameworkPropertyMetadata(default(object), RegisterLostFocus));
private static void RegisterLostFocus(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
//This is the control that is attached to it e.g. ContentControl
var sender = dependencyObject as FrameworkElement;
//Your ViewModel
var viewModel = dependencyPropertyChangedEventArgs.NewValue as ViewModel;
if (sender != null)
{
sender.LostFocus += (o, args) =>
{
//Close whatever you are doing right now to close the View.
viewModel.Close();
};
}
}
And on your view you can attach whatever ViewModel you want to close when it loses focus e.g. your SettingsView got LostFocus it'll close that view. In here I created an attached property on my MainWindow class.
<!-- Settings views -->
<ContentControl
MainWindow.CloseViewOnLostFocus="{Binding RelativeSource={RelativeSource Self},Path=Content}"
x:Name="SettingsView" Panel.ZIndex="2" Grid.Row="1" Grid.Column="0" Content="{Binding CurrentPageViewModel}"/>
<!-- Main page view -->
<ContentControl x:Name="MainPageView" Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="2" Width="1000" Height="700" Content="{Binding StaticPageViewModel}"/>
You can have a ContextMenu attached to the button, which is opened when the button is clicked. This way, the close-when-unfocused behaviour is entirely automatic.
You can then just restyle the menu to look however you want.

ItemsControl button click command

I need some quick help which is a road blocker for me now. I have Button in ItemsControl and I need to perform some task on Button click. I tried adding Command to Button in ItemsControl DataTemplate but its not working. Can anyone suggest how to proceed further.
<UserControl.Resources>
<DataTemplate x:key="mytask">
<TextBox Grid.Row="5" Grid.Column="2" Text="{Binding Path=PriorNote}" Grid.ColumnSpan="7" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,5" Width="505" Foreground="Black"/>
<StatusBarItem Grid.Row="2" Grid.Column="8" Margin="8,7,7,8" Grid.RowSpan="2">
<Button x:Name="DetailsButton" Command="{Binding CommandDetailsButtonClick}">
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl Grid.Row="1"
ItemsSource="{Binding ListStpRules}"
ItemTemplate="{StaticResource myTaskTemplate}" Background="Black"
AlternationCount="2" >
</ItemsControl>
</Grid>
and in ViewModel I have implemented code for Command. And its not working. Please suggest any solution for me to proceed further
The DataContext of each item in your ItemsControl is the item in the collection the ItemsControl is bound to. If this item contains the Command, your code should work fine.
However, this is not usually the case. Typically there is a ViewModel containing an ObservableCollection of items for the ItemsControl, and the Command to execute. If this is your case, you'll need to change the Source of your binding so it looks for the command in ItemsControl.DataContext, not ItemsControl.Item[X]
<Button Command="{Binding
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}},
Path=DataContext.MyCommand}" />
If your ViewModel has a property of type ICommand you can bind the Button's Command property to that:
XAML:
<DataTemplate DataType="{x:Type my:FooViewModel}">
<Button Content="Click!" Command="{Binding Path=DoBarCommand}" />
</DataTemplate>
C#:
public sealed class FooViewModel
{
public ICommand DoBarCommand
{
get;
private set;
}
//...
public FooViewModel()
{
this.DoBarCommand = new DelegateCommand(this.CanDoBar, this.DoBar);
}
}
Read this:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Implement a class similar to RelayCommand in the above article. Would make your further MVVM coding easier. :-)
Just a guess. Is CommandDetailsButtonClick defined in a ViewModel, which is DataContext of your UserControl (the one with ListStpRules property)?
DataContext of button in ItemTemplate is an item from ListStpRules, and if you command is not there then binding won't find it.
You can check diagnostic messages from wpf in Output window while debugging your application. It writes there if it can not resolve binding.

Child control blocking parent's event handler

I have a user control which has a registered MouseDown eventhandler. The user control has a ScrollViewer with an ItemsControl and a TextBlock. The visibility of the ItemsControl and TextBlock is toggled by the MouseDown eventhandler so at a time only one of them is visible. The eventhandler is invoked correctly when I click the TextBlock but not in the ItemsControl.
If I set the "IsHitTestVisible" on the ItemsControl to false the view's eventhandler gets exposed but I'm not able to scroll.
Can someone suggest a way out please?
Code is somewhat like this:
<Grid>
<Border>
<ScrollViewer>
<TextBlock>
</ScrollViewer>
<Border>
<Border>
<Grid>
<ItemsControl/> <!-- Has a ScrollViewer in template to show scroll bar -->
</Grid>
<Border>
</Grid>
Your UI scenario sounds kinda strange to me, but if you still want to implement it then you can do the following. You are not getting event raised for the paren becaus ItemsControl marks it as handled, so it is not routed further. You can subscribe to PreviewMouseDown event instead of MouseDown in the parent. Then no matter what the parent will receive notification before it's children.
UPDATE
You can do the following
<ScrollViewer>
<Grid MouseDown="HandleMouseDown">
<TextBlock />
<ItemsControl IsHitTestVisible="false"/>
</Grid>
</ScrollViewer>
So you will have scroll for text block and for items. ItemsControl's own scroll bars will not be used.
UPDATE 2
If you want to have separate ScrollViewers, then another possibility can be the following (although I think some of borders and grids are really unneeded. Only if they are functional in your snippet)
<Grid>
<Border>
<ScrollViewer>
<TextBlock MouseDown="HandleMouseDown">
</ScrollViewer>
<Border>
<Border>
<Grid>
<ScrollViewer>
<Grid MouseDown="HandleMouseDown">
<ItemsControl IsHitTestVisible="false"/>
</Grid>
</ScrollViewer>
</Grid>
<Border>
</Grid>

Resources