Posts Tagged ‘XAML’

postheadericon Creating WPF UIs for PowerShell with PowerBoots and Visual Studio WPF Designer

I’ve had several people ask me how PowerBoots compares to PrimalForms, or ask for a visual designer for PowerBoots. I usually answer something along the lines of the fact that Microsoft has already created a very good WPF/XAML designer in Visual Studio (including the free Express editions), particularly in 2010, so I don’t see why I should duplicate their efforts. However, up until now I haven’t written or published any sort of walk through about how to make that work …

So crank up your Visual Studio Express Edition and make yourself some user interfaces!

Basically, you just create a WPF project in C# or VB.Net or whatever … I chose to name my project “XamlForBoots” — your project will start off with an empty MainWindow.xaml file which will look something like this:

This is what the Main Window looks like at first

After you drag a few more controls onto the form, and you’ve created a complete user interface, you should delete the x:Class attribute. You need to make sure you know the name of the controls you want to interact with, so you might end up with something like this (I used the property pane, which you can’t see in this shot, to name each textbox and the button):

Once you've dragged some controls onto it

At this point, we’re ready to drop into PowerShell and write some script. Now … there is one catch here. The first script I’m going to show you here is for PowerBoots 0.3 (which will be the first release candidate for a gold 1.0 release, and will be out soon™). However, I’ll post below some code to make it work on the current release, but it requires an external function.

So, in the next release, you can just do something like this:

New-BootsWindow -FileTemplate $pwd\MainWindow.xaml -On_Loaded {
   Register-BootsEvent -InputObject $Calculate -EventName Click -Action {
      $Total.Text = '${0:n2}' -f (($Miles.Text -as [Double]) / ($Mpg.Text -as [Double]) * ($Cost.Text -as [Double]))
   }
}

The key thing you’re supposed to notice here is that the named controls in the XAML are automatically surfaced as variables in the event handlers, and all you have to do is write your logic and hook it up to the controls. We provide a Register-BootsEvent cmdlet which is like Register-ObjectEvent except that it executes the event handlers on the UI thread (so they can do things to the UI) instead of in a new runspace, but basically it’s like calling Add_Click. Of course, you can use Register-ObjectEvent if you just want to spin off PowerShell tasks that don’t read/write the UI.

Backwards compatibility

To get this to work in the current release, you need a function Export-NamedControl to create variables for each of those named controls as variables. Once you’ve defined that function, you can write code very much like I did before, but you have to call Export-NamedControl yourself, and you won’t have that Register-BootsEvent cmdlet. You don’t really need it for this anyway, so that’s not a big deal, at least in this case. Here’s the function. and the actual code to create the window. You can just paste this into a script file:

function global:Export-NamedControl {
[CmdletBinding()]
param(
   [Parameter(ValueFromPipeline=$true, Position=1, Mandatory=$true)]
   $Root = $BootsWindow
)
process {
   Invoke-BootsWindow $Root {
      $control = $BootsWindow
      while($control) {
         $control = $control | ForEach-Object {
            $Element = $_
            if(!$Element) { return }
   
            Write-Verbose "This $($Element.GetType().Name) is $Element"
 
            if($Element.Name) {
               Write-Verbose "Defining $($Element.Name) = $Element"
               Set-Variable "$($Element.Name)" $Element -Scope 2
            }
            ## Return all the child controls ...
            @($Element.Children) + @($Element.Child) + @($Element.Content) +
            @($Element.Items) + @($Element.Inlines) + @($Element.Blocks)
         }
      }
   }
}
}

####################################################################################################################
## Create the window and hook the click. Make sure to use full paths
New-BootsWindow {} -FileTemplate $pwd\MainWindow.xaml -On_Loaded {
   Export-NamedControl -Root $Args[0]
   $Calculate.Add_Click({
      $Total.Text = '${0:n2}' -f (($Miles.Text -as [Double]) / ($Mpg.Text -as [Double]) * ($Cost.Text -as [Double]))
   })
}

And in order to try my demo, you’re going to need that MainWindow.xaml file:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="Trip Cost">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="110" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Label Content="Miles" VerticalAlignment="Top" />
        <TextBox Grid.Column="1" Height="23" Width="200" Name="miles" HorizontalAlignment="Stretch" />
        <Label Grid.Row="1" Content="Miles per Gallon" VerticalAlignment="Top" />
        <TextBox Grid.Column="1" Grid.Row="1" Height="23" Width="200" HorizontalAlignment="Stretch" Name="mpg" />
        <Label Grid.Row="2" Content="Cost per Gallon"  VerticalAlignment="Top" />
        <TextBox Grid.Column="1" Grid.Row="2" Height="23" Width="200" HorizontalAlignment="Stretch" Name="cost"  />
        <Button Name="calculate" Grid.Row="3" Content="_Calculate" HorizontalAlignment="Center" />
        <TextBlock Grid.Column="1" Grid.Row="3" Height="23" Width="200" HorizontalAlignment="Stretch" Name="total" />
    </Grid>
</Window>

postheadericon PowerBoots: Loading XAML Windows in PowerShell 1.0 or 2.0

Awhile back I wrote a series of posts about WPF From PowerShell From PowerShell” which were about how you could load XAML in previous PowerShell 2 CTPs to create WPF user interfaces … a few people have mentioned loading XAML in PowerBoots, and a couple of people have posted other samples showing XAML even since I published the most recent release, so I figure it’s time to point out that you really can load that XAML into Boots, and get all the threading and other support.

Just for fun, I’m going to rehash an earlier post about updating windows to show how you can go about this using PowerBoots, and hopefully show that it’s a little easier (and a lot more async). Compare and contrast the code in this article with that one, just for fun.

This works with any version of PowerShell

Unlike the original article, most this code (except where explicitly mentioned) works on either PowerShell v2 with PowerBoots, or PowerShell 1.0 with PoshWPF, a snapin that is part of the PowerBoots module but is also released separately… Also, unlike those previous posts, this does not require you to be running PowerShell.exe with the -STA switch, since the New-BootsWindow cmdlet takes care of threading for us.

Read the rest of this entry »

postheadericon WPF Window “Native” Behavior: Snap-To Screen Edges

There are many desirable behaviors for Windows applications that are just much harder to do than they should be with the tools that Microsoft has provided in the .Net Framework. In WPF, many of these behaviors are even harder to create than in Windows Forms because the necessary hooks take a bit more work to write (thanks to the fact that there’s no window handles, so dropping down to “Native” code is harder. Luckily, WPF Attached Properties give us a whole new way to reuse these behaviors once they’re written.

I’ve started working on a few classes I’m calling NativeBehaviors, because they rely on intercepting the native Window Message processing, and I’ve put together a simple framework to let me write them, which I thought I would share. The first one I wrote is a SnapToBehavior, which implements a feature that people seem to be constantly re-implementing in apps. Basically, it makes a window snap to the screen edge when it gets close (and prevents them from being moved off-screen). I’ve also written a Global HotkeysBehavior which lets you register Hotkeys that work whenever your app is running (even if another app is active) so you can create a Hotkey to hide your app and show it, or whatever.

In the rest of this article I’ll show you how to achieve this in WPF using my base NativeBehavior class, and how to use it on a Window. Since some of you won’t really care how it works, let’s start with:

How to make your WPF Windows snap to screen edges in 3 easy steps:

Step 1. Add a reference to the Huddled.Wpf.Interop library.
Step 2. Add my Xml namespace to your root Window element
Step 3. Paste three lines from below….


<Window x:Class="GlobalHotkeys.DemoWindow" x:Name="DemoWindow"
   Title="GlobalHotkeys" Height="300" Width="300"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:huddled="http://schemas.huddledmasses.org/wpf"
   >
<!-- You just add a reference to the library, add the "huddled" namespace, and paste: -->
    <huddled:Native.Behaviors>
        <huddled:SnapToBehavior SnapDistance="20" />
    </huddled:Native.Behaviors>
<!-- The rest of your window can be whatever you like. -->
    <Grid>
        <Label Content="Drag this window near the screen edges"/>
    </Grid>
</Window>
 

I should point out the “SnapDistance” property of the SnapToBehavior is actually a WPF “Thickness” which lets you specify the distance from the screen edge as a single number to use on all sides, or as a separate number for each side: Left, Top, Right, Bottom. And that’s pretty much all there is to know.

How to implement a NativeBehavior.

The implementation of the SnapToBehavior is actually ridiculously simple, given the NativeBehavior framework. I simply created a SnapToBehavior class which derives from NativeBehavior, and implemented the single mandatory method:


/// <summary>
/// Gets the MessageMappings for this behavior:
/// A single mapping of a handler for WM_WINDOWPOSCHANGING.
/// </summary>
/// <returns>An enumerable collection of MessageMapping objects.</returns>
public override IEnumerable<MessageMapping> GetHandlers()
{
   yield return new MessageMapping(
      NativeMethods.WindowMessage.WindowPositionChanging, // the message
      OnPreviewPositionChange); // the delegate which will handle that message
}
 

When my new behavior is added to the Behaviors collection, my handler will be registered, and whenever the WM_WINDOWPOSCHANGING message arrives, it will be called. Now I defined a DependencyProperty for the SnapDistance, so that you could set that in XAML. It’s extremely simple, and VisualStudio has a built-in snippet for dependency properties, but here’s the code:


public static readonly DependencyProperty SnapDistanceProperty =
   DependencyProperty.Register(
      "SnapDistance",           // The name of the property (must match)
      typeof(Thickness),        // The type of the values
      typeof(SnapToBehavior),   // The type this property shows up on
      new UIPropertyMetadata(new Thickness(20)) // The default value
   );

public Thickness SnapDistance
{
   get { return (Thickness)GetValue(SnapDistanceProperty); }
   set { SetValue(SnapDistanceProperty, value); }
}
 

Once I have that, the last piece of the puzzle is to actually handle the window position changing message (think of it as an event, if you’re not used to Win32 message-based programming).

The basics of handling WM_WINDOWPOSCHANGING is that you get a structure passed in which has the proposed position of the Window, (including it’s z-index related to other apps) and you can basically tweak that however you like. Obviously there are lots of possibilities for this single message: always-on-bottom windows, undraggable windows, etc., but in our case we’re just concerned about how close we are to the edge.

We use the SystemParameters class to get the VirtualScreenLeft, VirtualScreenTop, and VirtualScreenWidth and VirtualScreenHeight which define the rectangle we’ll use for snapping. In the case of non-rectangular arrangements of multiple monitors this isn’t quite sufficient, but for our example anything more would be a distraction. Then we just check to see if the proposed position is within the “SnapDistance” of any of the edges, and if so, we change the position to be against the edge. That’s really all there is to it.

If you look at the code example below you’ll see I have to “Marshal” the WindowPosition structure in and out of an IntPtr which is passed in the WindowMessage … that’s the downside of intercepting window messages that the framework doesn’t already translate for us, but in this particular case, it’s actually fairly trivial.


/// <summary>lParam for WindowPositionChanging
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct WindowPosition
{
   public IntPtr Handle;
   public IntPtr HandleInsertAfter;
   public int Left;
   public int Top;
   public int Width;
   public int Height;
   public WindowPositionFlags Flags;

   public int Right { get { return Left + Width; } }
   public int Bottom { get { return Top + Height; } }
}  

      private IntPtr OnPreviewPositionChange(IntPtr wParam, IntPtr lParam, ref bool handled)
      {
         bool updated = false;
         var windowPosition = (NativeMethods.WindowPosition)Marshal.PtrToStructure(lParam, typeof(NativeMethods.WindowPosition));

         if ((windowPosition.Flags & NativeMethods.WindowPositionFlags.NoMove) == 0)
         {
            // If we use the WPF SystemParameters, these should be "Logical" pixels
            Rect validArea = new Rect(SystemParameters.VirtualScreenLeft,
                                      SystemParameters.VirtualScreenTop,
                                      SystemParameters.VirtualScreenWidth,
                                      SystemParameters.VirtualScreenHeight);

            Rect snapToBorder = new Rect(SystemParameters.VirtualScreenLeft + SnapDistance.Left,
                                     SystemParameters.VirtualScreenTop + SnapDistance.Top,
                                     SystemParameters.VirtualScreenWidth - (SnapDistance.Left + SnapDistance.Right),
                                     SystemParameters.VirtualScreenHeight - (SnapDistance.Top + SnapDistance.Bottom));

            // Enforce left boundary
            if (windowPosition.Left < snapToBorder.Left)
            {
               windowPosition.Left = (int)validArea.Left;
               updated = true;
            }

            // Enforce top boundary
            if (windowPosition.Top < snapToBorder.Y)
            {
               windowPosition.Top = (int)validArea.Top;
               updated = true;
            }

            // Enforce right boundary
            if (windowPosition.Right > snapToBorder.Right)
            {
               windowPosition.Left = (int)(validArea.Right - windowPosition.Width);
               updated = true;
            }

            // Enforce bottom boundary
            if (windowPosition.Bottom > snapToBorder.Bottom)
            {
               windowPosition.Top = (int)(validArea.Bottom - windowPosition.Height);
               updated = true;
            }

         }
         if (updated)
         {
            Marshal.StructureToPtr(windowPosition, lParam, true);
         }

         return IntPtr.Zero;
      }
 

Download it, or get the source code

If you’d like, you can download the current Huddled Interop for WPF library right now, or you can check out the source from CodePlex SVN or just download the most recent commit (You are only interested in the “Huddled” project which is in the “trunk”, not the “trunk-3.5”). The source is licensed freely under the BSD or GPL v2 (and a few other licenses, see the top of the source code files).

Either way you’ll get not just the SnapToBehavior but also the global HotkeysBehavior, and the Native Behaviors framework which I’ll write more about later, but for now, in case you want to try to use it, here’s an example using both the SnapTo and HotkeysBehavior (you can find this in the GlobalHotkeys demo project, which includes some sample code for handling hotkey conflicts). Enjoy.


<Window x:Class="GlobalHotkeys.DemoWindow" x:Name="window"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:huddled="http://schemas.huddledmasses.org/wpf"
   Title="Demo Window!" Height="300" Width="200"
   >
    <huddled:Native.Behaviors>
        <huddled:SnapToBehavior SnapDistance="80,20,80,10" />
        <huddled:HotkeysBehavior>
            <KeyBinding Command="huddled:GlobalCommands.ActivateWindow" Key="K"  Modifiers="Win" />
            <KeyBinding Command="huddled:GlobalCommands.CloseWindow"    Key="F4" Modifiers="Win" />
            <KeyBinding Command="huddled:GlobalCommands.ToggleWindow"   Key="S"  Modifiers="Win" />
        </huddled:HotkeysBehavior>
    </huddled:Native.Behaviors>
    <Grid>
        <Label Content="Drag this window near the screen edges"/>
    </Grid>
</Window>
 
Archives