Making the Windows Phone toolkit ListPicker ItemsPanel display a WrapPanel

by Shawn 7. May 2013 23:46

There are many things that should be part of the Windows Phone SDK and the ListPicker is one of them. There are so many aspects of the phone that use a control that the ListPicker was based on. One of these is the accent color picker. In Phone 7 the phone would display a vertical list of colors with the name of the colors next to it. In Phone 8 they changed this experience and went with a WrapPanel.

AccentColors

If you want to to get this same experience, you’ll want to use the ListPicker from the Windows Phone Toolkit. The ListPicker will first display what appears to be a ComboBox, yet when you tap this ‘ComboBox’ it opens up and takes over the entire phone. This is all pretty standard and the ListPicker covers this perfectly. Where it fails us is when we want to change how the ListPicker stacks it’s items. This is generally done by changing the ItemsPanel of an ItemsControl. Unfortunately the ListPicker does not allow you to change the ItemsPanel. If you download or browse the source for the ListPicker, you’ll see that it uses a PhoneApplicationPage that has a ListBox in it. The code behind of this page set the ItemTemplate based on a property from the ListPicker, but this is the only one it sets based on a property of the ListPicker.

So here we are with a great control from the toolkit. It allows us to do pretty much everything we need to, but it does not allow us to display a color picker like the native accent color picker. There are two ways that we can accomplish this. The first is to download the source and change it to allow for the ability to override the ItemsPanel. Downloading and changing source is usually risky. It’s risky because you then loose out on any updates that the maintainers may push out. It’s ok provided that you’re ok merging updates with your source.

The second it much easier, does not require you to download source or change any of the toolkit. This simple change requires you to create an implicit style for the ListBox control. Implicit styles allow you to specify a style that all controls of that type will use. To accomplish this, place the following style within your app.xaml

<Style TargetType="ListBox">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <toolkit:WrapPanel />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
</Style>

With this in your app.xaml, you can display a WrapPanel within the ListPicker. Unfortunately, with this style, all ListBoxes will display a WrapPanel. If you have other ListBoxes within your app, they will display WrapPanels. You can get the other ListBoxes to display as normal by adding the following style to your app.xaml.

<Style x:Key="StandardListBoxStyle" TargetType="ListBox">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="BorderBrush" Value="Transparent"/>
    <Setter Property="Padding" Value="0"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBox">
                <ScrollViewer x:Name="ScrollViewer" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" Padding="{TemplateBinding Padding}">
                    <ItemsPresenter/>
                </ScrollViewer>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>


Now use this style within your app.

<ListBox Style="{StaticResource StandardListBoxStyle}" ItemsSource="{Binding MyItems}"/>

If you use this approach and you have another ListPicker that you want to use in FullMode, it will display a WrapPanel. If you don’t want to always display a WrapPanel within your ListPicker, or if you don’t want to set the style manually then you’ll need to modify the source code of the ListPicker.

To begin you’ll need to download the source for the Windows Phone Toolkit. The toolkit comes with four projects, two under a Windows Phone 7 folder and two under a Windows Phone 8 folder. The projects use the same code for most of the files. These files have a central location and are added as a link.  You’ll need to modify one file that is shared and another file within each phone 7 and phone 8 project. Open the ListPickerPage.xamlcs file from both the phone 7 and phone 8 project. The ListPicker already has a FullModeItemTemplate, we can continue this style by adding a FullModeItemsPanel.

/// <summary>
/// Gets or sets the ItemsPanel.
/// </summary>
public ItemsPanelTemplate FullModeItemsPanel { get; set; }

Within the OnNavigatedTo method, add the following

if (FullModeItemsPanel != null)
{
    Picker.ItemsPanel = FullModeItemsPanel;
}

Now open the LickPicker.cs file from one of the toolkit projects (not a samples project). Add the following dependency property.

/// <summary>
/// Gets or sets the ItemsPanel used to display the items when ListPickerMode is set to Full.
/// </summary>
public ItemsPanelTemplate FullModeItemsPanel
{
    get { return (ItemsPanelTemplate)GetValue(FullModeItemsPanelProperty); }
    set { SetValue(FullModeItemsPanelProperty, value); }
}
 
/// <summary>
/// Identifies the FullModeItemsPanel DependencyProperty.
/// </summary>
public static readonly DependencyProperty FullModeItemsPanelProperty =
    DependencyProperty.Register(
    "FullModeItemsPanel", 
    typeof(ItemsPanelTemplate), 
    typeof(ListPicker), 
    new PropertyMetadata(null));

Within the OnFrameNavigatedTo method you’ll want to add the following line.

_listPickerPage.FullModeItemsPanel = FullModeItemsPanel;

It’s best to add this line under where the FullModeItemTemplate is set for the page.

I said earlier that changing the source can be risky because you loose out on updates that come from the maintainers of the source. Unfortunately there is little effort going into any of the toolkits from Microsoft and we have yet to see a toolkit for Windows Store apps. Not only does no one work on these toolkit, but you cannot provide contributions. Pedro Lamas has taken the source from codeplex and put it up on github. Pedro is providing fixes and taking pull requests for this repro. I’ve already provided this change and you can download it and start going.

Tags: ,

Windows Phone

Using the WebAPI to send errors for your apps

by Shawn 26. January 2013 22:55

If your app does anything at all complex, it’s going to crash. You’re going to miss checking something for null. You’re going to use a resource that doesn’t exist. Your app is going to crash. Luckily there are some ways to make sure you know what’s happening. Little Watson by Andy Pennell shows us a great way to send application errors but requires the user to email them to you. Bjorn Kuiper extended Little Watson by removing the need to email the report and instead send it to a PHP endpoint. This is a great solution provided your server is setup to run PHP . If you don’twant to use PHP, don’t want to install PHP or if you are using a hosting service that does not run PHP then you’re out of luck.

Instead of convincing your web provider to install PHP you could create a WCF service, but that seems like a lot of overhead. You could also create an ASP.NET web page and put the error details into a query parameter, but that seems hokie. Luckily the Web API has been introduced. The Web API makes this easy to implement, easy to deploy, and easy to use. Web API allows you to send an object of information in a well known format. Overall, the Web API is just awesome.

The first step is to start with the minimal Web API project. Open the Global.asax file and add the following line inside the Application_Start method

RouteTable.Routes.MapHttpRoute("ErrorRoute", "{controller}/{id}", new { id = RouteParameter.Optional });

Right click on the on controllers folder and create a new ApiController.

image

Following Bjorn’s example, we need an ExceptionContainer class that will hold our exception information. I’ve added an additional property for the application name. This is handy if you have more than one app.

public class ExceptionContainer
{
    ///  <summary>  
    /// Gets or sets the message.  
    /// </summary>  
    public string Message { get; set; }
 
    ///  <summary>  
    /// Gets or sets the stacktrace.  
    /// </summary>  
    public string StackTrace { get; set; }
 
    /// <summary>
    /// The application the error was found.
    /// </summary>
    public string ApplicationName { get; set; }
}

Inside the controller we need one method that we can send exception info to. The Web API allows you to take advantage of the HTTP verbs and structure your API in terms of them. For this error reporting, the most logical verb is POST. We’re not trying to GET, update (PUT) or DELETE anything. Create a new Post method that returns an HttpResponseMessage and takes an ExceptionContainer as a parameter. In the method we’ll send an email.

public HttpResponseMessage Post(ExceptionContainer exception)
{
    if (exception == null) return new HttpResponseMessage(HttpStatusCode.PaymentRequired);
 
    var message = new System.Net.Mail.MailMessage
        {
            From = new System.Net.Mail.MailAddress("phoneApps@yourdomain.com"),
            Subject = "Error report: " + exception.ApplicationName,
            Body = exception.Message + Environment.NewLine + exception.StackTrace
        };
    message.To.Add("you@domain.com");
 
    System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient("localhost", 25);
    // send credentials if your SMTP server requires them
    //client.Credentials = new NetworkCredential("user", "password");
    client.Send(message);
 
    return new HttpResponseMessage(HttpStatusCode.Created);
}

Deploy this up to your site and you’re ready to start sending application errors from your app.  Inside your app you simply need to package up the error and send to your server.

public void SendException(string uri, ExceptionContainer exception)
{
    try
    {
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.NullValueHandling = NullValueHandling.Ignore;
        string postStr = JsonConvert.SerializeObject(exception, settings).Replace("\"", "'");
 
        WebClient client = new WebClient();
        client.Headers[HttpRequestHeader.ContentType] = "application/json";
        client.UploadStringAsync(new Uri(uri), postStr);
    }
    catch { }
}

Remember that you should ask the user if you can send error reports, or give them the option to turn it on or off.

Tags: , , ,

Using a custom UriMapper to navigate to a login screen

by Shawn 18. June 2012 23:54

When writing a Windows Phone app you might need to prompt the user with login information. Maybe you want to know who in the family is using the app, or the most likely case is that you are trying to make an app that uses web services. When you want to give the user a login screen, you want this to be the first page that they see the first time they open the app. When they have already entered login information you don’t want to show the login screen and instead take them to the main page.

There are a few ways to do this. One way that is very common is to place logic into the MainPage.xaml.cs that checks if a login is needed and if so, navigate to the login screen. When doing this you need to take care to remove any backstack entries from the NavigationService when you get to the login page. You do this because if the user hits the back button, you want them to exit the app, not go to your main page. There are two logical places to put the code for this. One in the OnNavigatedTo override and the other in the Loaded event.

override void OnNavigatedTo(NavigationEventArgs e)
{
    // Check if we have login information for the user
    if(AppSettings.HasLoginInfo == false)
    {
        MavigationService.Navigate(new Uri("/LoginPage.xaml", UriKind.Relative));
    }
    else
    {
        // Login
    }
}

This works great most of the time, but I’ve had reports of crashes from this code. I have not been able to reproduce this, and I do not know if it is device specific, but I do know that some people have experienced an application crash from the above code. The error is

Navigation is not allowed when the task is not in the foreground.
   at System.Windows.Navigation.NavigationService.Navigate(Uri source)
   at MyApplication.MainPage.OnNavigatedTo(NavigationEventArgs e)

This is a horrible first impression of the application. I never tested placing the code into the Loaded event and see how that would fair. Another common way to show a login screen is to actually have the first page be the login page. This logic works exactly as the above example but in reverse. If you have login info, go to the main page.

One thing I never liked about this was the dependency that the pages had on each other. MainPage needed to know about login information and whether to go to the login page. The second solution has better logic to it. The login page manages the login info and it determines where the user should go. But I like having the page be dumb and only handle getting the login information. So I came up with a new solution. Using a custom UriMapper.

A UriMapper is used to map one URI to another (as you probably already guessed). UriMappers are generally used when you want to map a simple URI to a more complex URI, or when you want to remove knowledge about locations of URIs (or pages).  I wanted to use a UriMapper to remove the concern of which page to initially navigate to. To do this is really simple, you’ll need to modify the WMAppManifest.xml file and add (at least) two lines to your App.xaml.cs.

In the WMAppManifest.xml, change the NavigationPage attribute of the DefaultTask element to be some random page.

<DefaultTask Name="_default" NavigationPage="LaunchPage.xaml" />

The value that you use for the NavigationPage will be used within the UriMapper class. The mapper will take LaunchPage.xaml and map it to a real page based on if the user has provided login info already.

public class LoginUriMapper : UriMapperBase
{
    public override Uri MapUri(Uri uri)
    {
        if (uri.OriginalString == "/LaunchPage.xaml")
        {
            if (AppSettings.HasLoginInfo == false)
            {
                uri = new Uri("/LoginPage.xaml", UriKind.Relative);
            }
            else
            {
                uri = new Uri("/MainPage.xaml", UriKind.Relative);
            }
        }
        return uri;
    }
}

The mapper does not navigate to the page, it simply maps one Uri to another. A side effect of the mapper is that you can now navigate to LaunchPage.xaml. This means that from any page you can use the NavigationService.Navigate method to navigate to it. Because the mapper has already mapped LaunchPage.xaml to a real page, it will navigate to that real page. This side effect also means that if you navigate from MainPage to Page1 and push the back button, the NavigationService will attempt to navigate to LaunchPage. Everything will be fine in these cases provided that the RootFrame of your application has the custom UriMapper set.

Next you’ll need to modify the Application_Launching and Application_Activated events that your App.xaml.cs is subscribed to. I also think placing your login info here is a good place. It may not be the best, but it is better than in the MainPage or in your UriMapper. It is possible to handle logging in within the mapper as well, but that goes beyond what the mapper is suppose to do.

private void Application_Launching(object sender, LaunchingEventArgs e)
{
    RootFrame.UriMapper = new LoginUriMapper();
 
    if (AppSettings.HasLoginInfo)
    {
        Login();
    }
}
 
private void Application_Activated(object sender, ActivatedEventArgs e)
{
    if (e.IsApplicationInstancePreserved == false)
    {
        // tombstoned! Need to restore state
        RootFrame.UriMapper = new LoginUriMapper();
        
        if (AppSettings.NotLoggedIn)
        {
            Login();
        }
    }
}

As I mentioned above, everything will be fine provided that the application RootFrame has the UriMapper set. So it is very important to sete the mapper when the application is tombstoned. If you do not, you’ll get an error that it cannot find LaunchPage.xaml.

I’ll let you decide how to store the login information, whether or not the user has already logged in and how to login.

Tags:

Changing the background color of your pivot headers

by Shawn 23. May 2012 00:13

Let’s say you’re trying to put some style into your app and you really want a nice background color to all of your PivotItem titles. How would you go about doing this? You’re probably thinking “Shawn, that’s easy, just modify the HeaderTemplate on the Pivot.” And I would probably agree with you. Let’s try that out.

<controls:Pivot Title="MY APPLICATION">
    <controls:Pivot.HeaderTemplate>
        <DataTemplate>
            <Grid Background="Red">
                <TextBlock Text="{Binding}"/>
            </Grid>
        </DataTemplate>
    </controls:Pivot.HeaderTemplate>
    <!--Pivot item one-->
    <controls:PivotItem Header="item1"/>
 
    <!--Pivot item two-->
    <controls:PivotItem Header="item2"/>
</controls:Pivot>

Looking at the design view of this gives

image

We did accomplish changing the background color, but this looks horrible. I wanted a nice banner going across the headers. So how can we accomplish this? My two default answers Expression Blend, and msdn. Unfortunately, I have yet to find Windows Phone styles on msdn. So looks like it’s Expression Blend to the rescue! Creating a copy of the default style you’ll see

<controlsPrimitives:PivotHeadersControl x:Name="HeadersListElement" Grid.Row="1"/>

So the headers are in a control made just for the headers of a PivotItem. Interesting, but not surprising. Creating a copy of the style of this control and you’ll see the template defined as:

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="controlsPrimitives:PivotHeadersControl">
            <Grid>
                <Canvas x:Name="Canvas">
                    <ItemsPresenter/>
                </Canvas>
            </Grid>
        </ControlTemplate>
    </Setter.Value>
</Setter>

So we have a Grid and the background for the Grid is not set, not even using TemplateBinding. Luckily we can create a style for the PivotHeadersControl without needing to change any other styles. Add this style to the application resources (App.xaml) and it will apply to all pivots that are in your application.

<Style TargetType="controlsPrimitives:PivotHeadersControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="controlsPrimitives:PivotHeadersControl">
                <Grid Background="Red">
                    <Canvas x:Name="Canvas">
                        <ItemsPresenter/>
                    </Canvas>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Now with this style, we get the following:

image

Perfect! Well, maybe not. We do have the nice banner, but it is really close to the title of the Pivot. Going back to the style of the Pivot itself shows that the template for the title is defined as:

<ContentPresenter ContentTemplate="{TemplateBinding TitleTemplate}"
                  Content="{TemplateBinding Title}" 
                  Margin="24,17,0,-7"/>

It has a –7 margin for the bottom. To get a gap between the title and the banner of the headers you can either change the margin of the ContentPresenter (if modifying the template of the Pivot), or you can specify a TitleTemplate.

<controls:Pivot.TitleTemplate>
    <DataTemplate>
        <TextBlock Text="{Binding}" Margin="0,0,0,7"/>
    </DataTemplate>         
</controls:Pivot.TitleTemplate>

And now we have

image

Tags:

Enabling tilt on “non-selectable” items

by Shawn 23. April 2012 00:21

While working an a recent app I wanted to enable tilt functionality onto a Grid that contained a few items. Unfortunetly I was unable to get the control to tilt. I tried setting all sorts of properties, but nothing worked. I downloaded the source and took a look at it, and the same time I asked the twiiter universe whether non selectable items could be “tiltable” Morten Neilson replied with exactly what I was seeing.  His suggestion was to change the source which is easy enough to do. Just add FrameworkElement to the list.

static TiltEffect()
 {
     // The tiltable items list.
     TiltableItems = new List<Type>() 
     { 
         typeof(ButtonBase), 
         typeof(ListBoxItem), 
         typeof(MenuItem),
     };
 }

But I wanted to find a way to make this work without having to change the source for the WP7 toolkit. I love open source projects, I have two myself. But it’s a lot nicer to use the out of the box code rather than having to maintain your own.

My thought was if the toolkit limits to those items, what if I wrapped my Grid in one of them? I didn’t want to use a button because then I’d have to style it, so I tried a ListBoxItem.

<ListBoxItem toolkit:TiltEffect.IsTiltEnabled="True">
    <Grid>
        <TextBlock Text="I'm now tiltable!"/>
    </Grid>
</ListBoxItem>

Of course there is no need for the Grid in the above, but you get the point. This is being used outside of a ListBox and works fantastic!

Tags:

Windows Phone

My Hanselman inspired Windows Phone App

by Shawn 6. February 2012 19:03

It took me quite a long time to find a title for this blog. My first title was “Why I created a Hanselman like app”. My next title was “My Hanselman-ish app”, then “My first Windows Phone App”. Ultimately, this blog is about my app, Scott’s app, and app development in general.

Some may know that two days ago (2/2/2012) my first Windows Phone App was published. If you follow Scott Hanselman, it’s obvious I was inspired by him. Last night (2/3/2012) I posted a tweet that the app had been published the day before. I made a point to mention Scott in this tweet because I wanted him to know that he inspired me to get my butt into gear with app development.

How my app came to life

On Sunday (1/29/2012) I saw a tweet that Scott had his first app published. I took a look at it and thought “What a cool idea, how can I make it better?” So that night I stayed up until 3am writing something as easy to use, but had more to offer. When I was done, it was almost ready for the Market Place. The only thing missing was an icon. I thought his icon was great, but had lost it’s meaning in recent times, so Monday morning I asked a friend to design one that was like a flyer you would put on a power pole.  That night (1/30/2012) I submitted my app. If you are familiar with the Market Place, you know that it takes quite a few days to get an app published. My app was submitted on the 30th of January, published on the 2nd of February. I showed my app around to some coworkers and they all thought that it added a lot over what they had seen of Scott’s. Wednesday morning I showed my app to a co-worker and he said “Oh cool, you used a missing flyer like Hanselman mentioned.” My reply was “HUH? No he used a milk carton.” “Oh, he mentioned in his blog that not everyone understands the milk carton.” “…oh…” Keep in mind that Scott pushed the original version of that blog the same day I submitted my app.

If I had done a straight copy of Scott’s app I would even call myself a crook. I liked how simple it was, but I did not like that it didn’t offer more Windows Phone functionality. There were many things that I knew I could improve upon. So that’s what I set out to do that fine Sunday night. On the surface these two apps look very similar. But it’s what’s underneath that separates any two apps. By Monday morning my app had seven extra pieces of functionality (eight if you include text based input scope) on the edit page alone. Integration with the Picture Hub and other features were included as well to add more value.

I began working on v1.1 before 1.0 was even published. I submitted 1.1 on Feb 2nd, the same day that 1.0 was published. I hated the way you removed lines of text. While I followed one standard Windows Phone feature (hold to open delete context menu) it is very choppy on textboxes. I replaced that with the email like selection/deletion that the MultiselectList that the Toolkit gives you. I also added the ability to manipulate the image thanks to a post from Morten Nielson. This is something I know that Scott is currently implementing.

The motivation

I have been on again/off again with three separate app ideas for six months now. Pushing this app up, and doing it fast motivated me to get my app license and start doing those apps. When you don’t have the license you can easily find an excuse to not do development. But with the license, you now have the power to put apps up. The fact that you only have a year gets you even more motivated. My wife even commented that she was widowed to the app from the start to the end of v1.1.

The backlash

I figured there would be some people saying “you stole his idea”. The truth is, there are very few “original” ideas in app development these days. Even Lost Phone Screen was not original. It’s about what can your app offer over the other. Do people get angry when someone creates a new Twitter or foursquare app? No. People wonder “What can that app offer that this other app cannot.” Devs ask themselves “what does this app offer that mine does not, can I make it better?” The anger ensued not because the apps were similar, but because it’s Scott Hanselman. This is a guy who many devs have a man-crush on. I consider myself amongst those (just look at my “About the author” section). Scott is a man who I, you, we all admire and like.

Apps

What Lost Phone Screendoes not do, Missing Phonedoes do. As one twerson (what’s singular for tweeple?) said “Competition is healthy for business.” Competition makes apps better. You get to have your favorite Twitter, foursquare, and cocktail app because competition makes it the best. To be honest, I wanted to go head-to-head with the great Scott Hanselman. Not many people can say that they “one-up’ed” Scott. But competing with someone who has almost 50k Twitter followers, and who knows how many blog readers, is going to be hard. I know that Scott is working hard to get functionality into his app that mine already has. And that’s the point. To spur each other on, to do more, to make it better. One day I hope that I can see Scott in a conference, walk up, shake his hand, and say “I’m Shawn, I’m the one who ‘stole’ your app”, then go and have a beer and chat.

Tags:

Windows Phone

Managing Extents within the ArcGIS WPF/Silverlight/etc. APIs

by Shawn 23. November 2011 17:25

Managing extents within the ArcGIS .Net client API’s is pretty simple. Esri has an example on the resources page. I implemented one for the ArcFM Silverlight SDK sample. Oddly enough, they are quite similar. I don’t remember copying theirs, but you never know. Like most things we as developers do, after a couple of years you look back and wonder “What was I thinking?” I look back at my original code and wonder why did I implement the Extents class the way that I did. I originally used one List to hold all of the Extents. This made some of the code pretty ugly. I’ve created a different implementation that utilizes two stacks. I have one class that manages all of the extents:

   1: public class Extents
   2: {
   3:     private readonly Stack<Envelope> _backStack = new Stack<Envelope>();
   4:     private readonly Stack<Envelope> _forwardStack = new Stack<Envelope>();
   5:  
   6:     public bool HasPreviousExtent
   7:     {
   8:         get { return _backStack.Count > 0; }
   9:     }
  10:  
  11:     public bool HasNextExtent
  12:     {
  13:         get { return _forwardStack.Count > 0; }
  14:     }
  15:  
  16:     public Envelope CurrentExtent { get; set; }
  17:  
  18:     public Envelope PreviousExtent
  19:     {
  20:         get
  21:         {
  22:             if (HasPreviousExtent)
  23:             {
  24:                 _forwardStack.Push(CurrentExtent);
  25:                 CurrentExtent = _backStack.Pop();
  26:                 return CurrentExtent;
  27:             }
  28:             return null;
  29:         }
  30:     }
  31:  
  32:     public Envelope NextExtent
  33:     {
  34:         get
  35:         {
  36:             if (HasNextExtent)
  37:             {
  38:                 _backStack.Push(CurrentExtent);
  39:                 CurrentExtent = _forwardStack.Pop();
  40:                 return CurrentExtent;
  41:             }
  42:             return null;
  43:         }
  44:     }
  45:  
  46:     public void Add(Envelope extent)
  47:     {
  48:         if (extent == null) return;
  49:  
  50:         if (CurrentExtent != null)
  51:         {
  52:             _backStack.Push(CurrentExtent);
  53:         }
  54:         CurrentExtent = extent;
  55:  
  56:         // If a new extent is added, then anything in our forward stack needs to be removed
  57:         _forwardStack.Clear();
  58:     }
  59: }

I’m a big fan of MVVM, so I’ll use a ViewModel to access the Extents. I’ll also be using the DelegateCommand from Microsoft.

   1: public class ViewModel
   2: {
   3:     public ViewModel()
   4:     {
   5:         Extents = new Extents();
   6:         PreviousExtent = new DelegateCommand(MovePreviousExtent, extent => Extents.HasPreviousExtent);
   7:         NextExtent = new DelegateCommand(MoveNextExtent, extent => Extents.HasNextExtent);
   8:         IsNewExtent = true;
   9:     }
  10:  
  11:     private Map _map;
  12:  
  13:     public Map Map
  14:     {
  15:         get { return _map; }
  16:         set
  17:         {
  18:             UnsubscribeMapEvents(_map);
  19:             SubscribeMapEvents(value);
  20:             _map = value; 
  21:         }
  22:     }
  23:  
  24:     public DelegateCommand PreviousExtent { get; private set; }
  25:     public DelegateCommand NextExtent { get; private set; }
  26:  
  27:     private bool IsNewExtent { get; set; }
  28:     private Extents Extents { get; set; }
  29:  
  30:     private void MovePreviousExtent(object obj)
  31:     {
  32:         // This extent should not be put onto the Extents stack.
  33:         IsNewExtent = false;
  34:         Map.ZoomTo(Extents.PreviousExtent);
  35:     }
  36:     
  37:     private void MoveNextExtent(object obj)
  38:     {
  39:         // This extent should not be put onto the Extents stack.
  40:         IsNewExtent = false;
  41:         Map.ZoomTo(Extents.NextExtent);
  42:     }
  43:  
  44:     private void UnsubscribeMapEvents(Map map)
  45:     {
  46:         if (map == null) return;
  47:         map.ExtentChanged -= Map_ExtentChanged;
  48:     }
  49:  
  50:     private void SubscribeMapEvents(Map map)
  51:     {
  52:         if (map == null) return;
  53:         map.ExtentChanged += Map_ExtentChanged;
  54:     }
  55:  
  56:     private void Map_ExtentChanged(object sender, ExtentEventArgs e)
  57:     {
  58:         // Only add the extent if it is "new" ie: Not a Previous or Next extent
  59:         if (IsNewExtent)
  60:         {
  61:             Extents.Add(e.NewExtent);
  62:         }
  63:         IsNewExtent = true;
  64:         PreviousExtent.RaiseCanExecuteChanged();
  65:         NextExtent.RaiseCanExecuteChanged();
  66:     }
  67: }

The joy of these classes is that they can be used within all of the .Net Client API’s! This can even be used within the new ArcGIS Runtime (currently in Beta). Using these within our application is now a breeze! Here is the xaml for a WPF Window

   1: <Window x:Class="ExtentNavigationApp.MainWindow"
   2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:         xmlns:esri="http://schemas.esri.com/arcgis/client/2009"
   5:         Title="MainWindow" Height="350" Width="525">
   6:     <Grid x:Name="LayoutRoot" Background="White">
   7:         <esri:Map x:Name="Map" >
   8:             <esri:ArcGISTiledMapServiceLayer Url="http://server.arcfmsolution.com/ArcGIS/rest/services/Landbase/MapServer"/>
   9:             <esri:ArcGISTiledMapServiceLayer Url="http://server.arcfmsolution.com/ArcGIS/rest/services/Electric/MapServer"/>
  10:         </esri:Map>
  11:         <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
  12:             <Button Content="Previous Extent" Command="{Binding PreviousExtent}" Height="25" Width="100"/>
  13:             <Button Content="Next Extent" Command="{Binding NextExtent}" Height="25" Width="100"/>
  14:         </StackPanel>
  15:     </Grid>
  16: </Window>

And the code behind:

   1: public partial class MainWindow : Window
   2: {
   3:     public MainWindow()
   4:     {
   5:         InitializeComponent();
   6:         DataContext = new ViewModel() { Map = Map };
   7:     }
   8: }

Tags: , , , ,

ArcGIS WPF/Silverlight/Phone SDK

Creating a Custom MessageBox for Windows Phone Applications

by Shawn 13. November 2011 00:40

UPDATE: After posting this blog I found out about the message box within the XNA framework. This does allow for custom button text which is what I was trying to accomplish. However, the user experience is different than what you get from the message boxes within the native phone applications (eg: deleting a text). With the native message boxes, the application bar disappears, but with the XNA message box, it gets greyed out. It’s the little things that matter. Also within the XNA framework you cannot add additional components to the message box. For example, you might want to add a “Do not show me this again” option within the message box.

While using a Windows Phone you get prompted every once in awhile by a message box. Custom Applications have them, even apps native to the phone has them. When deleting a text message or a contact you get a nice prompt asking you if you want to delete it. But there is something unique about these message boxes that separates them from the one that you and I get to have. The standard message box only allows for an “OK” and a “Cancel” button. The message boxes that are native to the phone have custom text. When you delete a text, you are prompted with buttons “delete” and “cancel”. Seeing as there is not a way to do this, you need to create your own. I’ve created a very simple sample that can be used.

The CustomMessageBox sample is based on the assumption that message boxes are “binary”. What I mean is that you get binary options, Yes/No, OK/Cancel, etc. So I’ve limited what is allowed to be a valid result.

   1: public enum CustomMessageBoxResult
   2: {
   3:     Yes, 
   4:     No,
   5:     // Not using this yet, but you could wire up to the back button of the phone to be a cancel
   6:     Cancel
   7: }

I don’t have the ability to have a Show method return the CustomMessageBoxResult so I’ll need an EventArg that will be used within an event.

   1: public class MessageBoxEventArgs : EventArgs
   2: {
   3:     public CustomMessageBoxResult Result { get; set; }
   4: }

The xaml is pretty straight forward. We need to "”block” the page the CustomMessageBox is for. To do this I made the control a grid so it will fill up everything. Then give it a background with an opacity that will block all clicks.

<Grid x:Class="Visual.Controls.MessageBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"             
             Margin="0">
    <Grid.Background>
        <SolidColorBrush Color="{StaticResource PhoneBackgroundColor}" Opacity=".5"/>
    </Grid.Background>
    <Grid x:Name="MessagePanel" Background="{StaticResource PhoneChromeBrush}"
          VerticalAlignment="Top" HorizontalAlignment="Stretch"
          toolkit:TiltEffect.IsTiltEnabled="True">
        <StackPanel Margin="12,12,12,18">
            <TextBlock x:Name="HeaderTextBlock" TextWrapping="Wrap"
                       Style="{StaticResource PhoneTextLargeStyle}"
                       FontFamily="{StaticResource PhoneFontFamilySemiBold}"
                       HorizontalAlignment="Left"/>
            <TextBlock x:Name="MessageTextBlock" TextWrapping="Wrap"
                       Style="{StaticResource PhoneTextNormalStyle}"
                       FontSize="{StaticResource PhoneFontSizeMedium}"
                       Margin="12,24,12,24"
                       HorizontalAlignment="Left"/>
            <Grid HorizontalAlignment="Stretch" Margin="0,6,0,0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button x:Name="YesButton" Click="YesButton_Click"/>
                <Button x:Name="NoButton" Grid.Column="1" Click="NoButton_Click"/>
            </Grid>
        </StackPanel>
    </Grid>
</Grid>

The message box has really one basic function, to ask the user a question. To accomplish that one function, we need to do three things.

  1. The message box must be put into the application. (Show method)
  2. When the user answers the question, the message box needs to be removed from the application. (Remove method)
  3. The message box needs to tell the application what the user picked. (Closed event)
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
 
using Microsoft.Phone.Controls;
 
namespace Visual.Controls
{
    public partial class MessageBox : Grid
    {
        private PhoneApplicationPage _page;
 
        private MessageBox()
        {
            InitializeComponent();
        }
 
        public event EventHandler<MessageBoxEventArgs> Closed;
 
        protected virtual void OnClosed(MessageBoxResult result)
        {
            // need to unsubscribe from the backkeypress
            _page.BackKeyPress -= Page_BackKeyPress;
 
            var handler = this.Closed;
            if (handler != null)
            {
                handler(this, new MessageBoxEventArgs { Result = result });
            }
            Remove();
        }
 
        public static MessageBox Show(string message, string caption, string yesButtonText, string noButtonText = null)
        {
            MessageBox msgBox = new MessageBox();
            msgBox.HeaderTextBlock.Text = caption;
            msgBox.MessageTextBlock.Text = message;
            msgBox.YesButton.Content = yesButtonText;
            if (string.IsNullOrWhiteSpace(noButtonText))
            {
                msgBox.NoButton.Visibility = Visibility.Collapsed;
            }
            else
            {
                msgBox.NoButton.Content = noButtonText;
            }
            msgBox.Insert();
            return msgBox;
        }
 
        private void Insert()
        {
            // Make an assumption that this is within a phone application that is developed "normally"
            var frame = Application.Current.RootVisual as Microsoft.Phone.Controls.PhoneApplicationFrame;
            _page = frame.Content as PhoneApplicationPage;
            _page.BackKeyPress += Page_BackKeyPress;
 
            // assume the child is a Grid, span all of the rows
            var grid = System.Windows.Media.VisualTreeHelper.GetChild(_page, 0) as Grid;
            if (grid.RowDefinitions.Count > 0)
            {
                Grid.SetRowSpan(this, grid.RowDefinitions.Count);
            }
            grid.Children.Add(this);
 
            // Create a transition like the regular MessageBox
            SwivelTransition transitionIn = new SwivelTransition();
            transitionIn.Mode = SwivelTransitionMode.BackwardIn;
 
            // Transition only the MessagePanel
            ITransition transition = transitionIn.GetTransition(MessagePanel);
            transition.Completed += (s, e) => transition.Stop();
            transition.Begin();
 
            if (_page.ApplicationBar != null)
            {
                // Hide the app bar so they cannot open more message boxes
                _page.ApplicationBar.IsVisible = false;
            }
        }
 
        private void Remove()
        {
            var frame = Application.Current.RootVisual as Microsoft.Phone.Controls.PhoneApplicationFrame;
            var page = frame.Content as PhoneApplicationPage;
            var grid = System.Windows.Media.VisualTreeHelper.GetChild(page, 0) as Grid;
 
            // Create a transition like the regular MessageBox
            SwivelTransition transitionOut = new SwivelTransition();
            transitionOut.Mode = SwivelTransitionMode.BackwardOut;
 
            ITransition transition = transitionOut.GetTransition(MessagePanel);
            transition.Completed += (s, e) =>
                {
                    transition.Stop();
                    grid.Children.Remove(this);
                    if (page.ApplicationBar != null)
                    {
                        page.ApplicationBar.IsVisible = true;
                    }
                };
            transition.Begin();
        }
 
        private void Page_BackKeyPress(object sender, CancelEventArgs e)
        {
            OnClosed(MessageBoxResult.Cancel);
            e.Cancel = true;
        }
        
        private void YesButton_Click(object sender, RoutedEventArgs e)
        {
            OnClosed(MessageBoxResult.Yes);
        }
 
        private void NoButton_Click(object sender, RoutedEventArgs e)
        {
            OnClosed(MessageBoxResult.No);
        }
    }
 
}

Now you can show your message box like such:

   1: var msgBox = CustomMessageBox.Show("Do you like Windows Phone.", "Custom Prompt", "I <3 WP", "No Thanks");

And you get the following:

image

Tags:

Windows Phone

About the author

Shawn KendrotShawn Kendrot is a Technical Lead at Telvent, specializing in GIS (ArcGIS) Desktop and Silverlight.

Month List

Page List

DISCLAIMER

The opinions expressed herein are my own personal opinions and do not represent my employer’s view in any way.