Pages

Friday, July 20, 2012

CharmFrame – Adding CharmFlyout to Grid Apps

It appears that adding CharmFlyouts to apps like those generated with the Grid App template is difficult. The problem comes from the fact that there is no MainPage.xaml. Placing the CharmFlyout in each page, like GroupedItemsPage, GroupDetailPage, and ItemDetailPage is fraught with problems as Martin Bennedik comments. Martin suggests placing the code in the LayoutAwarePage, and the UI in a UserControl. This makes a fair amount of sense and is worth pursuing.

Posts in this series:

In thinking about the problem, I wondered if there might be a different solution.  Again, the root of the problem in Grid Apps is that there is no MainPage that is always visible (in which we can embed our CharmFlyouts).  It turns out, though, there is another user interface element that is always visible that can suit our purposes.  Grid Apps have a Frame.  This can be seen here:

App.xaml.cs
var rootFrame = new Frame();

Our goal is to embed our CharmFlyouts into this frame.  It will take a few steps to get there, but I think the end result is worth it.  Let’s set the stage by creating a Metro Grid App and adding the CharmFlyout library to it:

  1. Install the NuGet Package Manager if you have not already.
  2. Create your Metro Grid application in Visual Studio.
  3. Select Manage NuGet Packages from the Project menu.
  4. Click Online. Search for CharmFlyout. Click Install.

Create a UserControl to host your flyouts and call it MyCharmFlyouts.  Here is what I created:

MyCharmFlyouts.xaml
<UserControl
   x:Class="CharmDemoGridApp.MyCharmFlyouts"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:local="using:CharmDemoGridApp"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:cfo="using:CharmFlyoutLibrary"
   mc:Ignorable="d">
    <Grid>
        <cfo:CharmFlyout
           x:Name="cfoAbout"
           Heading="My About"
           HeadingBackgroundBrush="#FF4E0000">
            <StackPanel>
                <TextBlock
                   FontSize="16">CharmFlyout by John Michael Hauck</TextBlock>
                <TextBlock
                   FontSize="16">For support:</TextBlock>
                <HyperlinkButton
                   Click="OnMailTo">support@bing.com</HyperlinkButton>
            </StackPanel>
        </cfo:CharmFlyout>
        <cfo:CharmFlyout
           x:Name="cfoSettings"
           Heading="My Settings"
           HeadingBackgroundBrush="#FF4E0000">
            <StackPanel>
                <TextBlock
                   FontSize="16">Setting A</TextBlock>
                <CheckBox />
                <TextBlock
                   FontSize="16">Setting B</TextBlock>
                <CheckBox />
                <TextBlock
                   FontSize="16">Setting C</TextBlock>
                <CheckBox />
            </StackPanel>
        </cfo:CharmFlyout>
    </Grid>
</UserControl>

 

MyCharmFlyouts.xaml.cs
using System;
using Windows.UI.ApplicationSettings;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace CharmDemoGridApp
{
    public sealed partial class MyCharmFlyouts : UserControl
    {
        public MyCharmFlyouts()
        {
            this.InitializeComponent();
            SettingsPane.GetForCurrentView().CommandsRequested += CommandsRequested;
        }

        private void CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
        {
            args.Request.ApplicationCommands.Add(new SettingsCommand("a", "My About", (p) => { cfoAbout.IsOpen = true; }));
            args.Request.ApplicationCommands.Add(new SettingsCommand("s", "My Settings", (p) => { cfoSettings.IsOpen = true; }));
        }

        private async void OnMailTo(object sender, RoutedEventArgs args)
        {
            var hyperlinkButton = sender as HyperlinkButton;
            if (hyperlinkButton != null)
            {
                var uri = new Uri("mailto:" + hyperlinkButton.Content);
                await Windows.System.Launcher.LaunchUriAsync(uri);
            }
        }
    }
}

Modify App.xaml.cs to use a frame that supports additional content, and add your new flyout user control to it.  One such handy frame is now included in the CharmFlyoutLibrary, called CharmFrame.

App.xaml.cs
var rootFrame = new CharmFrame { CharmContent = new MyCharmFlyouts() };

That’s it.

CharmFrame

CharmFlyout binaries reside here: http://nuget.org/packages/charmflyout/

CharmFlyout source resides here: http://charmflyout.codeplex.com/

26 comments:

  1. That is very interesting, I didn't realize you could use the Frame for customizing the UI. The MSDN documentation only discusses using the Frame for navigation.

    Can you add other stuff to it, like a master page? For example I have a progress control reused on every page, and maybe there will be a dialog allowing the user to upgrade from the trial to the full version.

    ReplyDelete
    Replies
    1. You have several options:
      1) Build a single-page application and place a Frame inside MainPage for all your page-able content. This would give you the most control in my opinion, and you would not need to use CharmFrame. You could place all kinds of pop-ups and flyouts and other application-wide ui elements in the MainPage.
      2) Dump all your progress, flyouts, dialogs into MyCharmFlyouts. Maybe rename it to MyAppWideStuff. This should give you a great amount of control without too much refactoring of your code.
      3) Take the source code for CharmFrame (at codeplex) and modify it to your heart's content.

      Delete
  2. HI after i use your CharmFlyout.
    i have a bug and i don't know why
    https://lh3.googleusercontent.com/-T3iysX0RFsM/UBdBDXarHRI/AAAAAAAABHU/ekrYzYxz_KY/s643/123.JPG

    thanks for help

    ReplyDelete
    Replies
    1. My suspicion is that you placed the CharmFlyout control inside a Grid that has more than one row. If you move CharmFlyout outside of the Grid (or set RowSpan="2") it should look fine.

      Maybe you can start with the source code at charmflyout.codeplex.com and make sure that works well for you. There should be two samples, one for single page apps and one for grid apps. Take a close look at the GridApp example and see where you deviate from that.

      Delete
    2. O, you are right
      i forgot to set the row span="2"

      thank you very much ;)

      Delete
  3. How to Enter one more page?
    For example
    Setting>"Click">My Setting>"Click">My About

    I still a learner of English

    Sorry /.\

    ReplyDelete
    Replies
    1. The demo code at codeplex shows settings page with two items.

      Delete
    2. http://charmflyout.codeplex.com/
      I try to find the demo code but i don't found it successful.
      Would you mind provide me the hyperlink?

      Thanks

      Delete
    3. CharmFlyout source resides here: http://charmflyout.codeplex.com/

      Delete
  4. I'm getting a first chance exception of type 'System.Exception' It is highlighting :

    public MyCharmFlyouts()
    {
    this.InitializeComponent();

    this line: --> SettingsPane.GetForCurrentView().CommandsRequested += CommandsRequested;
    }

    Any ideas?

    ReplyDelete
  5. Jeff, Did you get a chance to try the demo apps at codeplex? Maybe you can tell me first if both those apps run fine on your system. Sorry for not having the answer.

    ReplyDelete
  6. Hey John,

    Thanks for responding. I did download the examples and they do seem to work... It seems like I'm missing a SettingsPane? I'm new to Win8 and there are lots of errors I'm not familiar with yet. I'm trying to compare my project with the examples that do work.

    Thanks!

    ReplyDelete
  7. Ok I got it.

    //var rootFrame = new Frame();
    var rootFrame = new CharmFrame { CharmContent = new MyCharmFlyouts() };
    SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

    I had the var rootFrame = new Frame(); in the wrong place. I needed to put the above code in the App.xaml.cs file

    ReplyDelete
  8. Hi John,

    Your Charm Flyout is very useful. Thank you very much for saving a lot of times

    What I want to do is similar to eBay Windows 8 app. Sign in button is one of the item of grid view and login flyout is called when user pressed Sign In.

    Can you do that with your charm flyout? Basically I would like to know how to show flyout when user press a button.

    Thanks

    ReplyDelete
  9. Sun,

    My guess is that you already know how to add a button handler with code-behind to your LayoutAwarePage-derived class. Therefore I assume that you are finding it difficult to set the CharmFlyout.IsOpen property to true from your button handler.

    There are many ways to solve this problem, and I will offer two.

    1) The MVVM pattern would suggest that you bind the IsOpen property to a CharmFlyoutViewModel and then use some mechanism to connect IsOpen property of the CharmFlyoutViewModel to the command handler in your GridViewModel. One mechanism to connect the two would be the use of an Event Aggregator found in the many MVVM / Prism type frameworks. This would be my preferred solution.

    2) For you I added a quick and dirty solution to the demo code located at charmflyout.codeplex.com. Here is what I did:

    * Modified MyCharmFlyouts.xaml.cs to include a public ShowSettings() function.

    * Modified GroupedItemsPage.xaml.cs to call the ShowSettings() function in the ItemView_ItemClick handler.

    You can view the source code changes here: http://charmflyout.codeplex.com/SourceControl/changeset/18569.

    I hope this helps.

    ReplyDelete
    Replies
    1. Great Work John.

      That's exactly what I needed. Thanks again for your time and help.

      Cheers,
      Sun

      Delete
  10. Hello, I'm using the CharmFlyout control to display settings of my app.

    In my app the user can add accounts and I want to implement this like the Messages app default in Windows 8. All the accounts are shown on that page at the right side.

    I have a mainpage where the user can see all the available accounts. When the user had added an account I want that the mainpage refreshes the list of accounts.

    When I use the "Window activate" event of the mainpage (Window.Current.CoreWindow.Activated += CoreWindow_Activated;) this event is triggered when I close the default settings charm, but when I enter the CharmFlyout the event is not fired....

    Is there a way that I can trigger the close event of the CharmFlyout?

    ReplyDelete
    Replies
    1. Gert,

      Yes.
      See the sample app "CharmDemoApp" at charmflyout.codeplex.com.
      There you can see how I respond to the IsOpenChanged of a particular flyout.

      Here is a code snippet:
      void CfoSettingsIsOpenChanged(object sender, EventArgs e)
      {
      isOpenTextBlock.Text = "IsOpen is now " + cfoSettings.IsOpen;
      }

      An alternative to your approach would be to use a ViewModel where your list of accounts is in an ObservableCollection. Your views would then bind to this o.c. as their data source. Then any changes made by any view will be reflected in all other views immediately.

      I hope this helps.

      - John

      Delete
    2. Thanks for you reply John, I use a grid app, so I don't have a page in my project. So I think I cannot use to exemple that you have shown in to CharmDemoApp?

      Delete
    3. Gert,

      Sure you can! See CharmDemoGridApp's MyCharmFlyouts.xaml.cs.

      - John

      Delete
  11. This comment has been removed by the author.

    ReplyDelete
  12. Do you have a working downloadable example for me??
    because when i use your code i get this error message:
    Element not found. (Exception from HRESULT: 0x80070490)
    on this code:
    SettingsPane.GetForCurrentView().CommandsRequested += CommandsRequested;

    ReplyDelete
    Replies
    1. CharmFlyout source (including two demo apps) resides here: http://charmflyout.codeplex.com/
      Does this help?

      Delete
  13. there is a problem in the charms when i open the charms the microsoft pubcenter or webview overlaps the charms control

    ReplyDelete
  14. Is it expected behavior that the CharmFlyout doesn't render in the IDE at design time? Having to run the app to see your Settings UI isn't really a great way to operate. I checked the demo app on CodePlex and found the same problem. Is there a way to fix this that I'm missing?

    ReplyDelete
  15. The CharmFlyout template places the content within a Popup control and the Popup control is not playing nice with the designer. I have yet to discover a way to have the content of a popup control show up in the designer.

    One work around, which might seem horrible, would be to create a separate user control with a Grid, StackPanel, or whatever container you need, sized to match the flyout dimensions, and place your content in there with the help of the IDE. Then when the content is to your liking, copy it over to your flyout control. I know that is not ideal, but it would at least let you see your content in the designer.

    ReplyDelete

Note: Only a member of this blog may post a comment.