Changing how Caliburn.micro places your usercontrol on a window - wpf

I have an app using Caliburn.micro and a custom window manager. The window manager is creating my own Base Window so I can customize the look and feel across the application.
I would like to add some controls on the window something like:
<DockPanel>
<ContentPresenter Content="{Binding CustomContent}" />
<StatusBar Height="20" DockPanel.Dock="Bottom" Background="Blue"/>
</DockPanel>
I would like to have Caliburn put the usercontrol from my ViewModel in the ContentPresenter, but Caliburn is replacing the entire content of my window.
I did this in the window:
using System.Windows;
namespace CaliburnCustomWindow
{
public partial class WindowBase
{
public static readonly DependencyProperty CustomContentProperty = DependencyProperty.Register("CustomContent", typeof (object), typeof (WindowBase));
public object CustomContent
{
get { return GetValue(CustomContentProperty); }
set { SetValue(CustomContentProperty, value); }
}
public WindowBase()
{
InitializeComponent();
}
}
}
And then modified my WindowManager to do this:
using System.Windows;
using Caliburn.Micro;
namespace CaliburnCustomWindow
{
internal class AppWindowManager : WindowManager
{
protected override Window EnsureWindow(object model, object view, bool isDialog)
{
Window window = view as Window;
if (window == null)
{
if (view.GetType() == typeof (MainView))
{
window = new WindowBase
{
CustomContent = view,
SizeToContent = SizeToContent.Manual
};
window.Height = 500;
window.Width = 500;
}
window.SetValue(View.IsGeneratedProperty, true);
}
else
{
Window owner2 = InferOwnerOf(window);
if (owner2 != null && isDialog)
{
window.Owner = owner2;
}
}
return window;
}
}
}
But it doesn't work. That binding to the CustomContent dependency property doesn't seem to work.
Is is possible to do this? If so how?

Could you not either use the default WindowManager implementation, and pass in a new instance of a wrapper DialogViewModel (and create the associated DialogView):
this.WindowManager.ShowDialog(new DialogViewModel(myViewModel));
or abstract this code in an implementation of an IDialogPresenter or similar if you wanted to simplify the client code:
this.DialogPresenter.Show(myViewModel);

Related

Windows narrator is not reading the custom tooltip content?

I have a requirement to read the content of complex tooltip content for WPF or UWP application by narrator. I am facing challenge to read the content visible for tooltip. Have tried to override the AutomationPeer class and there methods. But no luck :(
My XAML UI is below:
<Button Content="Submit" Grid.Row="2" Height="100" Width="200" >
<Button.ToolTip x:Uid="Addition_Details" >
<local:MyStackPanel Orientation="Vertical" Focusable="True" KeyboardNavigation.TabIndex="0" ForceCursor="True">
<TextBlock Text="Additional Details" KeyboardNavigation.TabIndex="1"/>
<TextBlock Text="Driver" KeyboardNavigation.TabIndex="2"/>
<TextBlock Text="A0221" KeyboardNavigation.TabIndex="3"/>
</local:MyStackPanel>
</Button.ToolTip>
</Button>
CustomGrid class is like:
public class MyStackPanel:StackPanel
{
protected override AutomationPeer OnCreateAutomationPeer()
{
return new UIAutomationChildPeer(this);
}
}
public class UIAutomationChildPeer : FrameworkElementAutomationPeer
{
public UIAutomationChildPeer(FrameworkElement element):base(element)
{
}
protected override string GetClassNameCore()
{
return "Additional Details";
}
protected override List<AutomationPeer> GetChildrenCore()
{
var childrenAutomationPeer = new List<AutomationPeer>();
var owner = Owner as StackPanel;
if (owner != null)
{
//owner.GotFocus += Owner_GotFocus;
var childElements = owner.Children;// indName("myGrid", owner) as Grid;
if (childElements != null && childElements.Count > 0)
{
foreach (TextBlock item in childElements)
{
var headerTextBlockAutomationPeer = new TextAutomationPeer(item);
childrenAutomationPeer.Add(headerTextBlockAutomationPeer);
}
}
}
return childrenAutomationPeer;
}
}
public class TextAutomationPeer : TextBlockAutomationPeer
{
private StringBuilder detail = new StringBuilder();
public UIElement Element { get { return Owner; } }
public TextAutomationPeer(TextBlock owner) : base(owner)
{
if (!string.IsNullOrWhiteSpace(owner.Text.ToString()))
{
detail.Append(owner.Text.ToString());
}
}
public override string ToString()
{
return detail.ToString();
}
}
Have tried with manually triggering the focus event or setting Tab Index. Nothing brings out the result. Any leads for resolving this issue.
#UWP #WPF #Windows10
The controls that you put in the tooltip won't get focused so the narrator won't respond to them. This is the same for TextBlock.
Generally, we will use the AutomationProperties.HelpText Attached Property to add simple text to a control. The narrator will respond to the AutomationProperties.HelpText.

How to bind command using ReactiveUI and WPF

I'm trying to learn ReactiveUI in WPF and I'm confusing on how to bind command using Reactive UI binding (not default Xaml binding).
I read on ReactiveUI documentation that the correct way is to use the following instruction:
this.BindCommand(this.ViewModel, vm => vm.MyCommand, v => v.myControl);
Now if I have in MainWindowView.xaml (View):
<Button x:Name="TestButton" Command="{Binding Click}" />
in MainWindowView code-behind:
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
and in MainWindowViewModel (ViewModel):
class MainWindowViewModel : ReactiveObject
{
public ReactiveCommand<Unit, Unit> ClickCommand { get; }
public MainWindowViewModel()
{
ClickCommand = ReactiveCommand.Create(ClickMethod);
}
void ClickMethod()
{
// Code for executing the command here.
}
}
I don't know where insert and how to compose the first instruction :
this.BindCommand(this.ViewModel, vm => vm.MyCommand, v => v.myControl);
for my specific context.
Thank you very much for and an answer.
The WPF samples referenced by Rodney Littles in the comment above are very good. For your case it should be something like this:
public partial class MainWindowView : ReactiveWindow<MainWindowViewModel>
{
public MainWindowView()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
this
.WhenActivated(disposables => {
this
.BindCommand(this.ViewModel, vm => vm.ClickCommand, v => v.TestButton)
.DisposeWith(disposables);
});
}
}
Make sure you derive from ReactiveWindow<MainWindowViewModel> instead of Window. Also, instead of DataContext, use the inherited property named ViewModel.

Redefine ContentControl.Content in WPF

I'm trying to create a dialog class in WPF. This class inherits from Window and provides some default buttons and settings.
The implementation basically looks like this:
namespace Commons {
public class Dialog : Window {
public new UIElement Content {
get { return this.m_mainContent.Child; }
set { this.m_mainContent.Child = value; }
}
// The dialog's content goes into this element.
private readonly Decorator m_mainContent = new Decorator();
// Some other controls beside "m_mainContent".
private readonly StackPanel m_buttonPanel = new StackPanel();
public Dialog() {
DockPanel content = new DockPanel();
DockPanel.SetDock(this.m_buttonPanel, Dock.Bottom);
content.Children.Add(this.m_buttonPanel);
content.Children.Add(this.m_mainContent);
base.Content = content;
}
public void AddButton(Button button) {
...
}
}
}
As you can see, I redefined the Content property.
Now I want to be able to use this dialog class in XAML like this:
<my:Dialog x:Class="MyDialogTest.TestDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:Commons;assembly=Commons"
Title="Outline" Height="800" Width="800">
<!-- Dialog contents here -->
</my:Dialog>
However, this will use Window.Content when setting the dialog's contents rather than Dialog.Content. How do I make this work?
You might need to indicate a property in "your" class as being the "content property" so that the child elements described by the XAML "content" of your Dialog get put into it instead of in the "content" property of your base Window.
[ContentProperty("Content")]
public class Dialog : Window {
If that doesn't work, then try changing the name.....so try this:
[ContentProperty("DialogContent")]
public class Dialog : Window {
public new UIElement DialogContent {
get { return this.m_mainContent.Child; }
set { this.m_mainContent.Child = value; }
}

How to navigate through windows with MVVM Light for WPF?

I've just started a new project in which the presentation layer will be done by WPF and MVVM Light by GalaSoft.
I need a lot of views and it's not clear to me how to manage navigation through windows.
First of all, the templates offered in MVVM Light for creating a new "WPF MVVM View" create a new Window that is not possible to use for navigation by frame (I mean, by putting a frame in mainView and changing the source path to navigate).
Do I simply have to change Window to Page for all the views I create using templates?
Or is there a different way to perform navigation in WPF with the MVVM Light toolkit?
I usually use a ContentControl to display dynamic content. It's Content property is usually bound to a CurrentViewModel property in the parent ViewModel, and DataTemplates are used to tell WPF how to draw the child ViewModels.
To change views, simply change the CurrentViewModel property in the parent ViewModel
You can find an example at this article of mine
Eventually I did it this way.
Following the idea of o_q, I created NavigationWindow as MainWindow and changed all the the views to page.
Then, I created an inteface and a class which using Navigation:
public interface INavigationService
{
event NavigatingCancelEventHandler Navigating;
void NavigateTo(Uri pageUri);
void GoBack();
}
public class NavigationService : INavigationService
{
private NavigationWindow _mainFrame;
#region Implementation of INavigationService
public event NavigatingCancelEventHandler Navigating;
public void NavigateTo(Uri pageUri)
{
if (EnsureMainFrame())
{
_mainFrame.Navigate(pageUri);
}
}
public void GoBack()
{
if (EnsureMainFrame()
&& _mainFrame.CanGoBack)
{
_mainFrame.GoBack();
}
}
#endregion
private bool EnsureMainFrame()
{
if (_mainFrame != null)
{
return true;
}
_mainFrame = System.Windows.Application.Current.MainWindow as NavigationWindow;
if (_mainFrame != null)
{
// Could be null if the app runs inside a design tool
_mainFrame.Navigating += (s, e) =>
{
if (Navigating != null)
{
Navigating(s, e);
}
};
return true;
}
return false;
}
}
Then, in viewModelLocator I created all the const string nedded to store the paths to my views:
public class ViewModelLocator
{
#region Views Paths
public const string FrontendViewPath = "../Views/FrontendView.xaml";
public const string BackendViewPath = "../Views/BackendView.xaml";
public const string StartUpViewPath = "../Views/StartUpView.xaml";
public const string LoginViewPath = "../Views/LoginView.xaml";
public const string OutOfOrderViewPath = "../Views/OutOfOrderView.xaml";
public const string OperativeViewPath = "../Views/SubViews/OperativeView.xaml";
public const string ConfigurationViewPath = "../Views/SubViews/ConfigurationView.xaml";
#endregion
}
In App.cs, in the Application_Startup event handler, with the help of Unity IoC I registered a singleton of NavigationService:
public partial class App : System.Windows.Application
{
private static IUnityContainer _ambientContainer;
public static IServiceLocator AmbientLocator { get; private set; }
...
private void Application_Startup(object sender, System.Windows.StartupEventArgs e)
{
_ambientContainer =
new UnityContainer();
_ambientContainer.RegisterType<INavigationService, NavigationService>(new ContainerControlledLifetimeManager());
AmbientLocator = new UnityServiceLocator(_ambientContainer);
ServiceLocator.SetLocatorProvider(() => AmbientLocator);
Now, in my ViewModelLocator, I can register a "Galasoft" message to catch all the events and navigate to a page; in the constructor I have:
public ViewModelLocator()
{
CreateMain();
CreateFrontend();
CreateBackend();
CreateStartUp();
CreateOperative();
CreateLogin();
CreateConfiguration();
CreateOutOfOrder();
// Set Startup Page...
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath, UriKind.Relative));
Messenger.Default.Register<MoveToViewMessage>(this, message =>
{
switch (message.StateInfo.StateType)
{
case StateType.StartUpState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath,UriKind.Relative));
break;
case StateType.LoginState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(LoginViewPath, UriKind.Relative));
break;
case StateType.OperativeState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(OperativeViewPath, UriKind.Relative));
break;
case StateType.ConfigurationState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(ConfigurationViewPath, UriKind.Relative));
break;
case StateType.ClosedState:
case StateType.OutOfOrderState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(OutOfOrderViewPath, UriKind.Relative));
break;
default:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath, UriKind.Relative));
break;
}
});
}
In this way I keep all the viewModels "ignorant"... they don't know anything about navigation, plus I don't have code behind.
If I need to navigate by using a button from a view I can resolve NavigationService from the connected viewModel and navigate to the Page I need.
And, most important thing, it works!
For a navigable application, you'll want your start up view to be a NavigationWindow instead of a Window
<NavigationWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MainWindow"
Title="My Application Title"
Height="300"
Width="400" />
Code behind:
using System.Windows.Navigation;
public partial class MainWindow : NavigationWindow
{
public MainWindow()
{
InitializeComponent();
}
}
The MVVM Light view templates use Window, but as you have guessed, you can just change it. If you want to be able to navigate to and from this view, make it a Page.
This is how you navigate:
<Page
x:Class="Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page1">
<Grid>
<!-- this button will navigate to another page -->
<Button
Content="Go to Page 2"
Click="Button_Click" />
</Grid>
</Page>
Code Behind:
using System.Windows;
using System.Windows.Controls;
public partial class Page1 : Page
{
public Page1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// the Page class has a property "NavigationService" which allows you to navigate.
// you can supply the "Navigate" method with a Uri or an object instance of the page
base.NavigationService.Navigate(new Page2());
}
}

Loading Views into ContentControl and changing their properties by clicking buttons

I have a mvvm(model view viewmodel) silverlight application that has several views that need to be loaded into ContentControls (i made it all in expression blend). What i dont know how to do is, for example, to load one view (user control) in one content control by clicking a button from another view that is in another content control. To make it easier to understand the problem, i need to do something similar to this:
http://www.codeproject.com/KB/silverlight/BlendableVMCom.aspx
with that difference that child1 and child2 are supposed to be loaded into theirown content controls by clicking Call child1 or call child2 buttons.
and example would be appreciated. Thanks in advance!
This example is very simplified, but I think you now how to adjust it to your application.
The main view:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border x:Name="commandsView">
<Button Content="Call view 1" Command="{Binding CallView1Command}" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="5" />
</Border>
<Border x:Name="displayedView" Grid.Column="1">
<ContentControl Content="{Binding CurrentView}" />
</Border>
</Grid>
I haven't created separated views as user controls, here are just borders, which can be replaced by real views.
Different view models for different views in code behind:
this.commandsView.DataContext = new CommandsViewModel();
this.displayedView.DataContext = new DisplayedViewModel();
First view model conains the command which sends the message to another view model:
public class CommandsViewModel
{
public CommandsViewModel()
{
this.CallView1Command = new RelayCommand(() =>
Messenger.Default.Send<View1Message>(new View1Message()));
}
public RelayCommand CallView1Command { get; set; }
}
public class View1Message : MessageBase
{
}
To make this example work, download the MVVM Light library.
The second view model receive the message and creates a view for its property:
public class DisplayedViewModel : ViewModelBase
{
public DisplayedViewModel()
{
Messenger.Default.Register<View1Message>(this, obj =>
this.CurrentView = new TextBlock { Text = "Pressed the button 1 and now here is the view 1" });
}
private object currentView;
public object CurrentView
{
get { return currentView; }
set
{
currentView = value;
RaisePropertyChanged("CurrentView");
}
}
}
Again, it is possible to use clr object instead of controls and apply data templates in xaml, but there will not be enough space to provide all the resulting code.
So that is all, the main idea is a some kind of event aggregator, which is the Messenger class in this particular case.
Without the MVVM Light it will require more code:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
var events = new GlobalEvents();
this.commandsView.DataContext = new CommandsViewModel(events);
this.displayedView.DataContext = new DisplayedViewModel(events);
}
}
public class GlobalEvents
{
public event EventHandler View1Event = delegate { };
public void RaiseView1Event()
{
View1Event(this, EventArgs.Empty);
}
}
/// <summary>
/// Commands which call different views
/// </summary>
public class CommandsViewModel
{
public CommandsViewModel(GlobalEvents globalEvents)
{
this.CallView1Command = new DelegateCommand(globalEvents.RaiseView1Event);
}
public DelegateCommand CallView1Command { get; set; }
}
/// <summary>
/// Model where views are changed and then displayed
/// </summary>
public class DisplayedViewModel : INotifyPropertyChanged
{
public DisplayedViewModel(GlobalEvents globalEvents)
{
globalEvents.View1Event += (s,e) =>
this.CurrentView = new TextBlock { Text = "Pressed the button 1 and now here is the view 1" };
}
private object currentView;
public object CurrentView
{
get { return currentView; }
set
{
currentView = value;
RaisePropertyChanged("CurrentView");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In this example you must change the DelegateCommand class for something different. Other code will work for everyone.
It sounds like you might be trying to do some sort of navigation. If that's true, check out the Silverlight navigation framework.

Resources