How can I draw a Polyline (in WPF) without anti-aliasing? [closed] - wpf

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I am trying to draw a polyline without anti-aliasing.
I think the answer may have something to do with setting the EdgeMode, but I've tried on various elements and cannot get a good result:
RenderOptions.SetEdgeMode(???, EdgeMode.Aliased)
Let's start from the ground up, because it seems like there are potentially a few places where something needs to be changed.
The polyline is drawn onto a StreamGeometry:
using (StreamGeometryContext gc = virtGeom.Open())
{
gc.BeginFigure(firstPoint, false, false);
gc.PolyLineTo(virtPoints, true, true);
}
That is then drawn into the DrawingContext of a DrawingVisual, which is eventually rendered to a RenderTargetBitmap which serves as the source for an Image.
Any ideas appreciated!
A complete example, that demonstrates the antialiasing, is shown below. This does not include any attempt to resolve it as various attempts to use SetEdgeMode produced no improvement.
MainWindow.xaml
<Window x:Class="Antialias.MainWindow"
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:local="clr-namespace:Antialias"
mc:Ignorable="d"
Title="MainWindow" Width="800" Height="400" Background="#FFCFCFCF" MouseDown="Window_MouseDown">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="Img" Width="320" Height="320"/>
<Image x:Name="Img2" Width="320" Height="320" Grid.Column="1"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Antialias
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
int w = (int)Img.Width;
int h = (int)Img.Height;
// Create RenderTargetBitmap and assign as image source
RenderTargetBitmap rtb = new RenderTargetBitmap(w, h, 96, 96, PixelFormats.Pbgra32);
Img.Source = rtb;
DrawingVisual dv = new DrawingVisual();
StreamGeometry geom = new StreamGeometry();
Pen pen = new Pen(Brushes.Red, 20)
{
StartLineCap = PenLineCap.Round,
EndLineCap = PenLineCap.Round,
LineJoin = PenLineJoin.Round
};
// Draw a simple line from top left to bottom right
Point startPoint = new Point(0, 0);
List<Point> points = new List<Point>()
{
new Point(w, h)
};
using (StreamGeometryContext gc = geom.Open())
{
gc.BeginFigure(startPoint, false, false);
gc.PolyLineTo(points, true, true);
}
// Apply white background and then draw geometry
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawRectangle(Brushes.White, null, new Rect(0, 0, w, h));
dc.DrawGeometry(null, pen, geom);
}
// Render to RTB
rtb.Render(dv);
// Part 2: Cut a clip (20 x 20) from the rendered bitmap and zoom in (using NearestNeighbor)
CroppedBitmap cbmp = new CroppedBitmap(rtb, new Int32Rect(0, 0, 20, 20));
RenderOptions.SetBitmapScalingMode(Img2, BitmapScalingMode.NearestNeighbor);
// Apply the zoomed sampled to the second image
Img2.Source = cbmp;
// Look for antialiasing (pixels part way between red and not white)
}
}
}

The obvious candidate for having the EdgeMode set to Aliased is the DrawingVisual, but setting it seemed to make no difference:
// This had no effect!
RenderOptions.SetEdgeMode(dv, EdgeMode.Aliased);
DrawingVisual has a protected property VisualEdgeMode, which presumably should have been set by calling the above. During debugging, I put a watch on the property and found it was not being set!
As it's a protected property you cannot set it directly, but you can create a subclass and do it that way. In the end, this is what worked for me:
class DrawingVisualAliased : DrawingVisual
{
public DrawingVisualAliased() : base()
{
VisualEdgeMode = EdgeMode.Aliased;
}
}
Not sure if this is the best way. It seems like the property should be set by calling RenderOptions.SetEdgeMode, so maybe there's something else not quite right.
Thanks to #Rekshino for pushing me to create an example. It certainly helped to be able to work on a very simplified version of the problem, with a clear visual representation of it.

Related

Zoom and pan WPF canvas

I have a question regarding WPF.
I have a Canvas that serves as a visual editor! I have a few 'nodes' positioned in the Canvas using 'X' and 'Y' properties (Canvas.Left and Canvas.Top). Now, I need this Canvas to let the user Zoom (in & out) and Pan around, as he want's to.
I implemented kind of a hack to emulate that behavior. This is the Code that let's the user 'pan' around in the Canvas:
///In file MainWindow.xaml.cs
private void ZoomPanCanvas_MouseMove(object sender, MouseEventArgs e) {
if (IsMouseDown) {
///Change the Cursor to Scroll
if (mNetworkUI.Cursor != Cursors.ScrollAll)
mNetworkUI.Cursor = Cursors.ScrollAll;
var currPosition = e.GetPosition(mNetworkUI);
var diff = currPosition - MouseLastPosition;
var p = new Point(diff.X, diff.Y);
mNetworkUI.ViewModel.Network.SetTransformOffset(p);
MouseLastPosition = currPosition;
}
}
///In file NetworkViewModel.cs
public void SetTransformOffset(Point newOffset) {
for (int i = 0; i < Nodes.Count; i++) {
Nodes[i].X += newOffset.X;
Nodes[i].Y += newOffset.Y;
}
}
Where the 'Nodes' are my editor-nodes displayed in the Canvas. The Zooming (with respect to the mouse position works as follows:
///File MainWindow.xaml.cs
private void ZoomPanCanvas_MouseWheel(object sender, MouseWheelEventArgs e) {
///Determine the Scaling Factor and Scale the Rule-Editor
var factor = (e.Delta > 0) ? (1.1) : (1 / 1.1);
currrentScale = factor * currrentScale;
ScaleNetwork();
///Translate the Nodes to the desired Positions
var pos = e.GetPosition(mNetworkUI);
var transform = new ScaleTransform(factor, factor, pos.X, pos.Y);
var offSet = new Point(transform.Value.OffsetX, transform.Value.OffsetY);
mNetworkUI.ViewModel.Network.SetTransformOffset(offSet);
}
///Also in MainWindow.xaml.cs
private void ScaleNetwork() {
mNetworkUI.RenderTransform = new ScaleTransform(currrentScale, currrentScale);
mNetworkUI.Width = ZoomPanCanvas.ActualWidth / currrentScale;
mNetworkUI.Height = ZoomPanCanvas.ActualHeight / currrentScale;
}
So, in the 'panning' I calculate the difference to the last mouse position and use that vector to manipulate the nodes, not the Canvas itself.
When I zoom, I determine the new zoom, set a new RenderTransform, resize the Canvas to again fill the provided space and again re-position the nodes in the Canvas.
It works very well for now. I can 'pan & zoom' around how I want, but I realized, that with many nodes present in my 'network' (connected nodes), things get quite slow.
One reason is, that on every movement of a node some events are raised resulting in a noticable delay when panning.
How is such a thing (without fixed Canvas-size and Scrollbars) possible in a performant manner? Is there a control out there that I can use? Is this possible with the Extended WPF toolkit's ZoomBox control?
Thank you!
I've written a Viewport control for this exact functionality.
I've also packaged this up on nuget
PM > Install-Package Han.Wpf.ViewportControl
It extends a ContentControl which can contain any FrameworkElement and provides constrained zoom and pan functionality. Just make sure to add Generic.xaml to your app.xaml
<Application.Resources>
<ResourceDictionary Source="pack://application:,,,/Han.Wpf.ViewportControl;component/Themes/Generic.xaml" />
</Application.Resources>
Usage:
<Grid width="1200" height="1200">
<Button />
</Grid>
The source code for the control and theme is on my gist and can be found on my github along with a demo application that loads an image into the viewport control.

Creating a Circular GUI

So one of my latest side projects is developing a application detection and populating assistant. Programmatically I am absolutely fine populating the backend code for what I want accomplished. But I've run into a road block on the GUI. I need a GUI that is a Quarter circle that extends from the task bar to the bottom right of a standard windows operating system. When the user doubleclicks on the application, the circle rotates into view. I can do this with a typical windows form that has a transparent background and a fancy background image. But the square properties of the form will still apply when the user has the application open. And I do not want to block the user from higher priority apps when the circle is open.
I'm not really stuck on any one specific programming language. Although, I would prefer that it not contain much 3d rendering as it is supposed to be a computing assistant and should not maintain heavy RAM/CPU consumption whilst the user is browsing around.
Secondarily, I would like the notches of the outer rings to be mobile and extend beyond the gui a mere centimeter or so.
I would not be here if I hadn't had scoured the internet for direction on this capability. But what I've found is application GUI's of this nature tend to be most used in mobile environments.
So my questions are: How can I accomplish this? What programming language can I write this in? Is this a capability currently available? Will I have to sacrifice user control for design?
I wrote out some code doing something close to what you described.
I’m not sure to understand how you do want the circle to appear, so I just let a part of it always visible.
And I didn’t get the part about the mobile outer ring.
Creating and placing the window
The XAML is very simple, it just needs a grid to host the circle’s pieces, and some attributes to remove window decorations and taskbar icon:
<Window x:Class="circle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Circle"
Width="250"
Height="250"
AllowsTransparency="True"
Background="Transparent"
MouseDown="WindowClicked"
ShowInTaskbar="False"
WindowStyle="None">
<Grid Name="Container"/>
</Window>
To place the window in the bottom right corner, you can use SystemParameters.WorkArea in the constructor:
public MainWindow()
{
InitializeComponent();
var desktopDim = SystemParameters.WorkArea;
Left = desktopDim.Right - Width;
Top = desktopDim.Bottom - Height;
}
Creating the shape
I build the circle as a bunch of circle pieces that I generate from code behind:
private Path CreateCirclePart()
{
var circle = new CombinedGeometry
{
GeometryCombineMode = GeometryCombineMode.Exclude,
Geometry1 = new EllipseGeometry { Center = _center, RadiusX = _r2, RadiusY = _r2 },
Geometry2 = new EllipseGeometry { Center = _center, RadiusX = _r1, RadiusY = _r1 }
};
var sideLength = _r2 / Math.Cos((Math.PI/180) * (ItemAngle / 2.0));
var x = _center.X - Math.Abs(sideLength * Math.Cos(ItemAngle * Math.PI / 180));
var y = _center.Y - Math.Abs(sideLength * Math.Sin(ItemAngle * Math.PI / 180));
var triangle = new PathGeometry(
new PathFigureCollection(new List<PathFigure>{
new PathFigure(
_center,
new List<PathSegment>
{
new LineSegment(new Point(_center.X - Math.Abs(sideLength),_center.Y), true),
new LineSegment(new Point(x,y), true)
},
true)
}));
var path = new Path
{
Fill = new SolidColorBrush(Colors.Cyan),
Stroke = new SolidColorBrush(Colors.Black),
StrokeThickness = 1,
RenderTransformOrigin = new Point(1, 1),
RenderTransform = new RotateTransform(0),
Data = new CombinedGeometry
{
GeometryCombineMode = GeometryCombineMode.Intersect,
Geometry1 = circle,
Geometry2 = triangle
}
};
return path;
}
First step is to build two concentric circles and to combine them in a CombinedGeometry with CombineMode set to exclude. Then I create a triangle just tall enough to contain the section of the ring that I want, and I keep the intersection of these shapes.
Seeing it with the second CombineMode set to xor may clarify:
Building the circle
The code above uses some instance fields that make it generic: you can change the number of pieces in the circle or their radius; it will always fill the corner.
I then populate a list with the required number of shape, and add them to the grid:
private const double MenuWidth = 80;
private const int ItemCount = 6;
private const double AnimationDelayInSeconds = 0.3;
private readonly Point _center;
private readonly double _r1, _r2;
private const double ItemSpacingAngle = 2;
private const double ItemAngle = (90.0 - (ItemCount - 1) * ItemSpacingAngle) / ItemCount;
private readonly List<Path> _parts = new List<Path>();
private bool _isOpen;
public MainWindow()
{
InitializeComponent();
// window in the lower right desktop corner
var desktopDim = SystemParameters.WorkArea;
Left = desktopDim.Right - Width;
Top = desktopDim.Bottom - Height;
_center = new Point(Width, Height);
_r2 = Width;
_r1 = _r2 - MenuWidth;
Loaded += (s, e) => CreateMenu();
}
private void CreateMenu()
{
for (var i = 0; i < ItemCount; ++i)
{
var part = CreateCirclePart();
_parts.Add(part);
Container.Children.Add(part);
}
}
ItemSpacingAngle define the blank between two consecutive pieces.
Animating the circle
The final step is to unfold the circle. Using a rotateAnimation over the path rendertransform make it easy.
Remember this part of the CreateCirclePart function:
RenderTransformOrigin = new Point(1, 1),
RenderTransform = new RotateTransform(0),
The RenderTransform tells that the animation we want to perform is a rotation, and RenderTransformOrigin set the rotation origin to the lower right corner of the shape (unit is percent).
We can now animate it on click event:
private void WindowClicked(object sender, MouseButtonEventArgs e)
{
for (var i = 0; i < ItemCount; ++i)
{
if (!_isOpen)
UnfoldPart(_parts[i], i);
else
FoldPart(_parts[i], i);
}
_isOpen = !_isOpen;
}
private void UnfoldPart(Path part, int pos)
{
var newAngle = pos * (ItemAngle + ItemSpacingAngle);
var rotateAnimation = new DoubleAnimation(newAngle, TimeSpan.FromSeconds(AnimationDelayInSeconds));
var tranform = (RotateTransform)part.RenderTransform;
tranform.BeginAnimation(RotateTransform.AngleProperty, rotateAnimation);
}
private void FoldPart(Path part, int pos)
{
var rotateAnimation = new DoubleAnimation(0, TimeSpan.FromSeconds(AnimationDelayInSeconds));
var tranform = (RotateTransform)part.RenderTransform;
tranform.BeginAnimation(RotateTransform.AngleProperty, rotateAnimation);
}
Not actually answering this, but I liked your question enough that I wanted to get a minimal proof of concept together for fun and I really enjoyed doing it so i thought I'd share my xaml with you:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing" x:Class="WpfApplication1.Window2"
Title="Window2" Height="150" Width="150" Topmost="True" MouseLeftButtonDown="Window2_OnMouseLeftButtonDown"
AllowsTransparency="True"
OpacityMask="White"
WindowStyle="None"
Background="Transparent" >
<Grid>
<ed:Arc ArcThickness="40"
ArcThicknessUnit="Pixel" EndAngle="0" Fill="Blue" HorizontalAlignment="Left"
Height="232" Margin="33,34,-115,-116" Stretch="None"
StartAngle="270" VerticalAlignment="Top" Width="232" RenderTransformOrigin="0.421,0.471"/>
<Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="41" Margin="51.515,71.385,0,0" Click="Button_Click" RenderTransformOrigin="0.5,0.5">
<Button.Template>
<ControlTemplate>
<Path Data="M50.466307,88.795148 L61.75233,73.463763 89.647286,102.42368 81.981422,113.07109 z"
Fill="DarkBlue" HorizontalAlignment="Left" Height="39.606"
Stretch="Fill" VerticalAlignment="Top" Width="39.181"/>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</Window>
And it looks like this:

Connecting two dynamically created shapes using line shape in silverlight

Im working on flowchart kind of application in asp.net using silverlight.. Im a beginner in Silvelight, Creating the elements (Rectangle,Ellipse,Line.. ) dynamically using SHAPE and LINE Objects in codebehind (c#)
These shapes will be generated dynamically, meaning I'll be calling a Web service on the backend to determine how many objects/shapes need to be created. Once this is determined, I'll need to have the objects/shapes connected together.
how to connect dynamically created shapes with a line in Silverlight like a flowchart.
I read the below article, but its not working for me, actualHeight & actualWidth of shapes values are 0.
Connecting two shapes together, Silverlight 2
here is my MainPage.xaml
<UserControl x:Class="LightTest1.MainPage">
<Canvas x:Name="LayoutRoot" Background="White">
<Canvas x:Name="MyCanvas" Background="Red"></Canvas>
<Button x:Name="btnPush" Content="AddRectangle" Height="20" Width="80" Margin="12,268,348,12" Click="btnPush_Click"></Button>
</Canvas>
code behind MainPage.xaml.cs
StackPanel sp1 = new StackPanel();
public MainPage()
{
InitializeComponent();
sp1.Orientation = Orientation.Vertical;
MyCanvas.Children.Add(sp1);
}
Rectangle rect1;
Rectangle rect2;
Line line1;
private void btnPush_Click(object sender, RoutedEventArgs e)
{
rect1 = new Rectangle()
{
Height = 30,
Width = 30,
StrokeThickness = 3,
Stroke = new SolidColorBrush(Colors.Red),
};
sp1.Children.Add(rect1);
rect2 = new Rectangle()
{
Height = 30,
Width = 30,
StrokeThickness = 3,
Stroke = new SolidColorBrush(Colors.Red),
};
sp1.Children.Add(rect2);
connectShapes(rect1, rect2);
}
private void connectShapes(Shape s1, Shape s2)
{
var transform1 = s1.TransformToVisual(s1.Parent as UIElement);
var transform2 = s2.TransformToVisual(s2.Parent as UIElement);
var lineGeometry = new LineGeometry()
{
StartPoint = transform1.Transform(new Point(1, s1.ActualHeight / 2.0)),
EndPoint = transform2.Transform(new Point(s2.ActualWidth, s2.ActualHeight / 2.0))
};
var path = new Path()
{
Data = lineGeometry,
Stroke = new SolidColorBrush(Colors.Green),
};
sp1.Children.Add(path);
}
what I am doing in button click event is just adding two rectangle shapes and tring to connect them with a line (like flowchart).
Please suggest what is wrong in my code..
Try replacing the line
connectShapes(rect1, rect2);
with
Dispatcher.BeginInvoke(() => connectShapes(rect1, rect2));
I'm not sure of the exact reason why this works, but I believe the shapes are only rendered once control passes out of your code, and only once they are rendered do the ActualWidth and ActualHeight properties have a useful value. Calling Dispatcher.BeginInvoke calls your code a short time later; in fact, you may notice the lines being drawn slightly after the rectangles.
The TransformToVisual method behaves in much the same way as the ActualWidth and ActualHeight properties. It will return an identity transformation if the shape hasn't been rendered. Even if your lines were being drawn with a definite width and height, they would end up being drawn all on top of one another at the top-left.
I also found that I needed to add the lines to the Canvas, not the StackPanel, in order for them to be drawn over the rectangles. Otherwise the StackPanel quickly filled up with lines with a lot of space above them.

Why does this simple LineSegment jump from canvas centre-bottom to top-left in Silverlight?

I have some very simple code that 'correctly' draws a short vertical black line on a 1024x768 blue canvas in WPF (well in Silverlight 4).
<UserControl x:Class="SimpleCanvas.MainPage"
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"
mc:Ignorable="d"
Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" Background="White">
<Canvas x:Name="PathCanvas" Width="1024" Height="768" Background="Blue"/>
</Grid>
</UserControl>
and here's the code-behind
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
var myPathFigure = new PathFigure
{
StartPoint = new Point(492, 748)
};
var line1 = new LineSegment
{
Point = new Point(492, 708)
};
myPathFigure.Segments.Add(line1);
var myPathGeometry = new PathGeometry();
myPathGeometry.Figures.Add(myPathFigure);
var myPath = new Path
{
Data = myPathGeometry,
Stretch = Stretch.Fill,
Stroke = new SolidColorBrush(Color.FromArgb(0xFF, 0x0, 0x0, 0x0)),
StrokeThickness = 10
};
PathCanvas.Children.Add(myPath);
}
Now if I change the end-point of the line segment, so that instead of just changing the Y from the start-point I also change the X, albeit by only one pixel, the whole line is rendered in the top left of the canvas. Here's the revised code-behind.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
var myPathFigure = new PathFigure
{
StartPoint = new Point(492, 748)
};
var line1 = new LineSegment
{
Point = new Point(491, 708)
};
myPathFigure.Segments.Add(line1);
var myPathGeometry = new PathGeometry();
myPathGeometry.Figures.Add(myPathFigure);
var myPath = new Path
{
Data = myPathGeometry,
Stretch = Stretch.Fill,
Stroke = new SolidColorBrush(Color.FromArgb(0xFF, 0x0, 0x0, 0x0)),
StrokeThickness = 10
};
PathCanvas.Children.Add(myPath);
}
If both rendered in the centre-bottom of the canvas, or both rendered in the top left of the canvas, I could understand. But I do not understand why the first code block causes the line to render centre-bottom and the second code block causes the line to render top-left.
Note that I'm not using Canvas.Top or Canvas.Left.
Any insight gratefully received!
The problem here is that you are creating the Path with the Stretch property set to Stretch.Fill. I imagine you want to leave the Path with Stretch set to the default, Stretch.None.
The reason why the first code-block causes the line to be rendered in the centre of the canvas is because both points in its single LineSegment have the same X-coordinate, and hence the Path has a width of 0. Clearly an element with width 0 can't be stretched horizontally. Silverlight could attempt to stretch your Path vertically, since it has nonzero height, but it seems it chooses not to.
Similarly, if you draw a horizontal line (so the Path has height 0), Silverlight doesn't stretch the line either.
In your second code-block, when you've change the X-coordinate, even by 1 pixel, you give the Path a non-zero width and height. Silverlight can then stretch it to fill the entire Canvas.
Note that the Path control itself, as the container for the various path segments, is being stretched, not the individual line segments that comprise it.

How to attach help to WPF application built in VS 2008

I have one chm for my application which i want to attach with my application that is when user press F1 attached help with the project opens up.
I do not know of any in built support in WPF to display CHM files. What I do is add an InputGesture to connect F1 keystroke to Application.Help command and in the Windows CommandBindings add a handler for Application.Help Command. Here is a sample code:
<Window x:Class="WpfTestApp.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" >
<Window.InputBindings>
<KeyBinding Command="Help" Key="F1"/>
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Help" Executed="HelpExecuted" />
</Window.CommandBindings>
<Grid>
</Grid>
Here's the handler code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void HelpExecuted(object sender, ExecutedRoutedEventArgs e)
{
System.Diagnostics.Process.Start(#"C:\MyProjectPath\HelpFile.chm");
}
}
Using F1 Help (CHM format) With WPF
Based on this approach, I did the following so I could take advantage of an OnlineHelpViewModel I had that was managing the help through a RelayCommand. When F1 is pressed, with this approach, the RelayCommand on the viewmodel is invoked just as if ia ? button had been pushed. In other words,we bind F1 to the RelayCommand.
This example uses GalaSoft MvvmLight.
DependencyProperty on the MainWindow
public static DependencyProperty HelpCommandProperty = DependencyProperty.Register("HelpCommand",
typeof(RelayCommand<string>), typeof(WindowExt),
new PropertyMetadata(null));
public RelayCommand<string> HelpCommand
{
get
{
return (RelayCommand<string>)GetValue(HelpCommandProperty);
}
set
{
SetValue(HelpCommandProperty, value);
}
}
OK that holds the command
Now in the window loaded event or somewhere you like:
...
Binding b2 = new Binding();
b2.Source = ViewModelLocator.OnlineHelpViewModelStatic;
b2.Path = new PropertyPath("ShowApplicationHelpCommand");
b2.Mode = BindingMode.OneWay;
this.SetBinding(HelpCommandProperty, b2);
var kb = new KeyBinding();
kb.Key = Key.F1;
kb.Command = HelpCommand;
this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Help, HelpCommand_Executed));
OK that binds the command on the SOURCE viewmodel to this window.
Then a handler for the command on this window ( perhaps this can be inline somehow)
private void HelpCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
this.HelpCommand.Execute(HelpContextGuid);
}
and you now can call the single help command on OnlineHelpViewModel from anywhere, and it can be arbitrarily complicated too depending. Note that the DP HelpContextGuid is passed - it is up to the command to decide what to do with it but the RelayCommmand<string> wants an argument
The command itself looks like (on the SOURCE Viewmodel)
...
ShowApplicationHelpCommand = new RelayCommand<string>(
(h) => { ShowApplicationHelp(h); },
(h) => CanShowApplicationHelpCommand);
...
and method it invokes is whatever it takes to show the help,
In my case, I create a RadWindow and so on and populated it with XamlHelp using the BackSpin Software HelpLoader. The help file is generated from Word with Twister4Word.
All of this is particular to my application so you would probably do something else to make a help window. Here is the constructor:
public MigratorHelpWindow()
{
// create local resources for desingn mode, so Blend can see the viewmodels
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
App.CreateStaticResourcesForDesigner(this);
}
InitializeComponent();
if (Application.Current.MainWindow != null)
{
var thm = ThemeManager.FromName(Application.Current.FindResource("TelerikGlobalTheme").ToString() ?? "Office_Blue");
StyleManager.SetTheme(this, thm);
}
// window configuration
MaxHeight = SystemParameters.WorkArea.Height;
MaxWidth = SystemParameters.WorkArea.Width;
Binding b = new Binding();
b.Source = ViewModelLocator.OnlineHelpViewModelStatic;
b.Path = new PropertyPath("ApplicationHelpFileName");
b.Mode = BindingMode.OneWay;
this.SetBinding(ApplicationHelpFileNameProperty, b);
if (String.IsNullOrEmpty(ApplicationHelpFileName))
{
UiHelpers.ShowError("No help file is available", true);
return;
}
// LOAD YOUR HELP HERE OR WHATEVER
// LOAD YOUR HELP HERE OR WHATEVER
// LOAD YOUR HELP HERE OR WHATEVER
HelpLoader.Load(ApplicationHelpFileName);
HelpLoader.Default.Owner = this;
HelpLoader.Default.HelpLayout = HelpLayout.Standard;
HelpLoader.Default.TocContainer = _mTOC;
HelpLoader.Default.IndexContainer = _mIndex;
HelpLoader.Default.TopicContainer = _mTopic;
HelpLoader.Default.SearchContainer = _mSearch;
HelpLoader.Default.FavoritesContainer = _mFavorites;
}
You can find the BackSpin Help Authoring tool here
http://www.backspinsoftware.com/site/Default.aspx
It generates compiled help from Word documents.

Resources