Thursday, February 28, 2013

Windows Store Apps, SVG, and HTML Interoperability

There are several reasons you may wish to show HTML content in your Windows Store app. You may have information that is updated frequently and makes the most sense to consume as HTML data. Your app may aggregate feeds that contain HTML content. In some cases you may be creating a native client that accesses an existing web-based application that you need to interoperate with. Fortunately, the WinRT has a control that addresses these needs: the WebView control.

It is important to understand what the purpose of the control is and how that may impact how you architect your app. The control itself is not intended to be a complete replacement for a browser. Indeed, Microsoft has made it clear that if you try to get an app approved in the store that tries to solely interact with a web-based app or simply act as a web browser, it will be rejected [1-Ten Things You Need to Know about WebView]. There are several limitations built into the control that prevents many scenarios.

The control uses the Internet Explorer 10 engine. It does not support advanced features in HTML5 including caching, indexed databases, programmatic access to the clipboard, or geo location. It supports the same version of the Document Object Model (DOM) that is used by Windows Store apps written in JavaScript [2 - Document Object Model (DOM) (Windows)]. There is no support for any type of plug-in or ActiveX extension, including Flash, Silverlight, or embedded PDF documents.

There is full support for enhanced protocol handling [3 - Auto-launching with file and URI associations]. This provides support for things like custom protocols that allow you to access resources embedded in your app as well as links that will automatically launch the program associated with them. You can generate a web page that, when viewed, not only provides content served from within your app, but also allows the user to open those resources using their preferred app (for example, a resource with a .pdf extension will open with the preferred reader app).

The control is also unique because it cannot be covered by other controls. It completely takes over the pixels defined for its area. Any controls that you attempt to place in front of the WebView control will be cropped. If you must overlay the page with controls, you can capture the current view using a special WebViewBrush and use that brush to render a surface.

The final and perhaps most important feature of the WebView control is the ability to interoperate with your Windows Store app. Your app can communicate with the web page by invoking JavaScript functions and passing in parameters. The page can also interact with your app by calling a method on an object that WinRT creates in the DOM. This allows you to create some truly interactive experiences between web content and your Windows Store app.

The WebViewExamples project contains several examples of working with HTML and JavaScript. MainPage.xaml defines a WebView control named WebViewControl and a brush named WebBrush. When the page is loaded, a handler is registered to the LoadCompleted event on the WebView. This will fire any time a new resource (whether it is a string literal or a web page) is loaded. It will force the brush to refresh and show a dialog indicating the content that was loaded.

private async void WebViewControlLoadCompleted(object sender, NavigationEventArgs e)
{     
WebBrush.Redraw();
     var url = e.Uri != null ? e.Uri.ToString() : "Text Content";
     var popup = new MessageDialog(url, "Content Loaded.");
     await popup.ShowAsync();

}

The first button triggers a straightforward page load.

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
     this.WebViewControl.Navigate(new Uri(JeremyBlog)); }

The page is my main blog page. You’ll find it loads and renders fine. You can also click on links and navigate just as if you pulled the page up in a web browser. There are no forward or backwards buttons and there is no place to enter a new address so you are stuck following the links provided by the control. Notice that the dialog fires every time you navigate to a new page – you could easily use this feature to inspect where the user ended up and hide the control or reset it if needed.

The second button invokes a specific page as a mobile client by writing a user agent that mimics an iPhone. My blog will detect that a mobile device is accessing the page and redirect to a mobile-friendly page. In this mode you may see several errors thrown by the debugger. This is because the WebView control will throw an error any time it encounters a JavaScript error or an issue parsing the DOM. You can turn off the JavaScript errors by going into the debugger settings and un-checking Script under the Just-In-Time tab. Other errors you have to muscle through and just select No – on my machine the mobile page generates four errors I must acknowledge before accessing the page. If you run the app outside of the debugger, it will swallow these errors and run normally.

The only way to update the user agent that the WebView control uses is to use some hacks that will prevent your app from making it into the app store. For those hackers in the audience, you’ll need to p/Invoke urlmon.dll and call UrlMkSetSessionOption with option 0x10000001 and your user agent string prior to navigating with the control. A more direct approach is to download the page yourself and pass it to the control as text. The steps are shown below.

private async void ViewMobile_OnClick(object sender, RoutedEventArgs e)
{
     var handler = new HttpClientHandler { AllowAutoRedirect = true };
     var client = new HttpClient(handler);
     client.DefaultRequestHeaders.Add("user-agent", MobileUserAgent);
     var response = await client.GetAsync(new Uri(JeremyYogaPost));   
response.EnsureSuccessStatusCode();
     var html = await response.Content.ReadAsStringAsync();
this.WebViewControl.NavigateToString(html);
}

The example sets up a client that accepts redirects (in the case of the blog post reference, it will redirect to a mobile version), then creates a client and sets the user agent to mimic a mobile device. The page is requested and the code ensures a successful response was obtained before loading the content of the response and sending it to the WebView control.

This technique is common for requesting content in a format that is suitable for viewing within your app (mobile content tends to have less chrome so it is easier to process). You can also parse and analyze the content prior to presenting it to the end user. This allows you to strip unwanted tags or even inject your own JavaScript and content as needed prior to the page being displayed.

In some apps you may wish to include embedded HTML resources for your apps to display. The third button demonstrates this technique. It also shows that the rendering engine has full support for Scalable Vector Graphics (SVG) [4 - W3C SVG Working Group], which can be useful if you need to integrate charts or vector-based graphics in your application and have existing implementations using SVG. In the example app, there is an embedded HTML file named Ellipse.html in the Data folder.

Referencing this resource is simple and straightforward. The special ms-appx-web protocol provides a path to the WebView control that is embedded in the store app. You’ll notice it uses three forward slashes and then provides a path to the resource in the app from the root.

private void ViewSvg_OnClick(object sender, RoutedEventArgs e)
{
     this.WebViewControl.Navigate(new
          Uri("ms-appx-web:///Data/Ellipse.html")); }

You can also embed static text (or dynamic text that you build and generate prior to passing into the control). The fourth button triggers the load of a string literal. The literal contains the text for an HTML document and includes various header tags, an embedded image (the logo of the app itself), and a hyperlink that demonstrates you can navigate to embedded content or external content. If you follow the link to the external website, you’ll see that the base URL is consistently reported as static text because that’s how the original document was loaded.

private void ViewString_OnClick(object sender, RoutedEventArgs e)
{
     this.WebViewControl.NavigateToString(HtmlFragment); }

The final button demonstrates how your app can interoperate with the web. To show this, I included a JavaScript function in the template of my blog. The function is just a few lines of code. If you load any page from my blog and view the source, you can search for “supersecret” to locate the function:

function superSecretBiographyFunction(subPath) {
   window.location.href="http://csharperimage.jeremylikness.com/" + subPath;
}

The function is usually not called, but that will change if you load one of the blog pages using the example app and click the last button. The code snippet below shows the implementation. The InvokeScript method is used to call the function. You can pass in null for a function that takes no parameters, otherwise you can pass in an array of values. In this case the path to my biography is passed in, and the page picks it up and navigates to the path. Note that you can call JavaScript on any type of page the control rendered, including embedded resources or literals you’ve passed in.

private async void CallJavaScript_OnClick(object sender, RoutedEventArgs e)
{
     MessageDialog popup = null;
     var parameters = new[]
                          {
                                 "p/biography.html"
                          };
     try 
{
        this.WebViewControl
.InvokeScript("superSecretBiographyFunction", parameters);
    }
    catch (Exception ex)
     {
        popup = new MessageDialog(ex.Message, "Unable to Call JavaScript.");
     }
     if (popup != null)
     {
         await popup.ShowAsync();
     } }

An attempt to invoke a function on page that doesn’t have that function will result in an exception. The code captures the very user-unfriendly exception and displays it for you. The pattern is important to note because it is not possible to use the await keyword from within a catch block. The workaround is to capture any necessary information needed in the catch, then await the dialog to display the information once the block is exited.

Although sending information to a page is nice, wouldn’t it be great if you could receive information back? This scenario is possible and you can in fact create apps that have two-way conversations with web pages. If you searched for the source snippet above, you may have also noticed this block of code:

if (document.title==='C#er : IMage: Synchronous to Asynchronous Explained') {
if ((typeof (window.external) !== "undefined") && (typeof (window.external.notify) !== "undefined")) {
      window.external.notify(new Date().toLocaleString());
   }
}

I would not normally do a string comparison against a topic title to trigger application logic, but I can get away with it here because I own the blog and am not planning on changing the title. When the user navigates to a specific topic inside a WebView control, the web page itself will actually send a message to the Windows Store app that contains the localized date and time. The communication between the web page and the Windows Store app is illustrated here:

htmlwinstoreinterop

The function first checks for the existence of the window.external object. This is provided to the JavaScript runtime by Internet Explorer to provide access to the object model of the host [5 - External object (Internet Explorer)] (in this case, the host is the WebView control). The control in turn exposes the notify method to the runtime. A call to this from the web page with a single string parameter will raise the ScriptNotify event on the WebView control.

The example app simple stores the value that is passed to a local variable.

private void WebViewControlScriptNotify(object sender, 
Windows.UI.Xaml.Controls.NotifyEventArgs e) {
     this.message = e.Value; }

Once the entire web page is loaded, the LoadCompleted event checks for this value and displays it when present.

if (string.IsNullOrEmpty(this.message))
{
     return; } popup = new MessageDialog(
     this.message,
      "The Blog Has Spoken!"); this.message = string.Empty; await popup.ShowAsync();

To see this in action, use the timeline on the right side of my blog to navigate to the year 2012, the month August, and the title “Synchronous to Asynchronous Explained.” The result should be two pop-ups, one to notify you when the page has loaded and a second that declares the blog has spoken and displays the date. This closes the loop and demonstrates how Windows Store apps can load, display, and interact with HTML-based content.

This post just scratches the surface of what is possible but hopefully provides you with an in-depth demonstration of how you can embed HTML, SVG, and even JavaScript in your Windows Store apps.

Full Source Code

Side-Load Package

Sunday, February 17, 2013

Review of the Lenovo IdeaPad Yoga 13 for Development

After several months of comparing various Windows 8 devices to replace my old 1.5” thick 6-lb. Dell laptop, I finally settled on the Lenovo IdeaPad Yoga 13. My requirements are fairly straightforward: I want 8GB of RAM and a minimum of 256GB SSD, I have to have a touch display and the resolution needs to be at least HD+ (1600 x 900). I wasn’t as concerned about budget and there was one device that fit the bill perfectly: the ASUS ZenBook Prime Touch. The only catch was that the 8GB version isn’t yet available in the states. I also didn’t feel like waiting for the Helix and other devices. The Dell XPS 12 was a strong contender but I had a hard time committing to such a small (12.5”) display, and the ThinkPad X1 Carbon Touch would have likely been my choice if Lenovo didn’t have supply problems that prevented them from shipping it for 4 – 8 weeks after ordering.

I did want to stick with the maxim of “thin and light” because part of the reason for replacing my perfectly competent Dell is the simple fact it gets old lugging something that thick and heavy around. The only two concerns I had with the Lenovo were the keyboard (the ThinkPad line has the best keyboards out there, but I also know I can purchase a USB external version if I need one) and the display (1600 x 900 is fine, but I really wanted full 1080p at 1920 x 1080). So, after much deliberation, I pulled the trigger and purchased my Lenovo from Amazon after locating a third-party seller who had it in stock (the 8GB models are suddenly very hard to find).

Un-boxing

UPS dropped off my box just a few days after I purchased it through Amazon.com. The box is simple and easy to open and gives you immediate access to the components within. It ships with the laptop, a power cable, and some documentation to get started quickly. Obviously there is no “instructional DVD” as there is no DVD included. I was debating whether or not to invest in a portable DVD burner, but I realized I rarely use the one I have unless it’s to install software, and then I can just burn an ISO to my home network and install from that. I passed on it for now.

I don’t do un-boxing videos but it was really mundane – a few slices with my Buck knife and I was in, then plugged in the adapter and booted it. The boot time is under 10 seconds. I spent just a few minutes logging into my Microsoft Account and specifying my WiFi options and I was logged in with most of my content synchronized. All of my Windows 8 roaming settings carried over and most of my writing is synchronized through SkyDrive. The installs for Office, SQL, and Visual Studio with a few extensions all took only an hour or two. It took a little longer to copy some resources over my local network but it wasn’t long before I was transitioned over to the new laptop.

Here’s a quick snap of the laptop sitting on the tray on my Delta flight:

airplane

Look and feel

The laptop itself is very simple. It has a solid build and feel. It’s not designed with a sleek taper like the MacBook, Samsung, or even ASUS models, but this is a 13.3” ultrabook and that means light (3.34 lbs – heavier than other tablets but half the weight of my Dell) and thin (17mm, again about half the thickness of my Dell). The outside is a nice monochrome silver metal (top and bottom – apparently bold users can get an orange one too) while the inside sports a black keyboard and frame around the screen. The inside looks almost like leather but is texture plastic. The touchpad is glass.

Here’s the laptop closed:

WP_000381

Specs

The system I ordered sports an Intel Core i7-3537U (U for Ultralow voltage) processor w/ 2.00 GHz that over-clocks up to 3.1 GHz. It is dual core with hyper-threading allowing for 4 virtual cores. The installed memory is 8.0GB and I’m happy to say after quite a bit of usage this laptop isn’t memory hungry even when running a full developer edition of SQL Server 2012 and building/running an MVC 4 enterprise application using Entity Framework that is composed of over 20 different projects. It compiles the project roughly 33% faster than my Dell that had the same specs, which I’m assuming I can attribute to the included Samsung SSD. From the specs I’ve read it’s midrange speed (over 200 MB both read and write, whereas some models like the Dell XPS 12 almost double the read rate). The Windows Experience Index rates it solid in almost everything but desktop graphics, but that’s the trade-off with the ubiquitous Intel HD 4000 chip – you’re getting it for business, not heavy 3D gaming.

Screenshot (8)

Included are Bluetooth 4.0 that works quite well (more on that later), Sensors include ambient light, orientation and compass. Orientation sensors are there as I’ve verified with several programs and I don’t believe there is GPS although the location services do quite well using the network to locate your position. There is no Near Field Communication (NFC) unfortunately.

Ports

Starting at the back of the device and going clockwise, we have some vents, square proprietary power adapter, USB 2.0, mini-card reader and orientation lock. The front includes the battery indicator, power button, and Lenovo one-touch restore. Finally we have volume rockers, headphone and microphone jack (single jack), USB 3.0 and full HDMI.

Keyboard

One reason I was reluctant to invest in the Yoga was my fear that the smaller keyboard would require me to invest in an external keyboard. I have been using an external one for some time but have been hoping to consolidate and just use the included laptop. The biggest complaints I’ve read are that the laptop feels cramped and there are issues with the location and size of the right shift key.

keyboard

After working on this laptop full time for several days, both developing code and working on my next book, I can say that my opinion is that this keyboard is phenomenal. Maybe I’m biased because I’m used to the really small ASUS VivoTab RT keyboard and this is much larger, but it truly has been a pleasure using the keyboard. I’m able to easily find the keys, they have perfect travel and feedback and a nice, soft touch (and yes, I do use the right shift key). It is much quieter than my external keyboard. While the locations of the Home, End, etc. keys are slightly different than what I’m used to, I quickly adapted to them and haven’t found it to be a problem. I took a typing test online that lasted one minute and was able to type 109 Word Per Minute at 100% accuracy, so no complaints here – I doubt I’ll be investing in an external keyboard.

The only complaint I have is the trend that seems to be common in newer ultrabooks and that’s making the function keys default to laptop functions instead of, well, function keys. The default mode is for the keys to do things like adjust volume, switch to airplane mode and change the brightness of the display. As a developer I’m constantly using function keys from within Visual Studio and it can be jarring to hit F10 to step through code and have the external monitor fly-out appear instead. I’m assuming there is some way to swap the default but I haven’t looked into it yet and I seem to be getting “trained” on holding down the FN key to make them work as expected.

Touchpad

I’ve seen a lot of negative touchpad reviews in general so I was concerned about this experience as well. I can say my fears were completely unfounded. I find the touchpad to be extremely responsive to both normal “desktop mode” mouse pointer emulation and full Windows 8 gesture recognition. I consistently use it to swap between applications, pull up the charm bar, activate the application bar and scroll both vertically and horizontally. It has the right level of sensitivity and I don’t miss the mouse at all. There are no raised buttons for clicks – you depress the lower left or right – but that is fine. Palm detection is superb and I have yet to have a “miss” where I’m typing and my palm forces the cursor to jump elsewhere.

My only complaint about the touchpad is that sometimes I’ll try to move the cursor and accidently swipe from the edge to activate a Windows 8 gesture instead. Fortunately the touchpad is fairly large so it is easy to adapt to this.

Screen

When I started my search for a replacement laptop, I was fairly certain I only wanted to consider full HD at 1080p. It was frustrating to find most of Lenovo’s offerings standardized at the lower 1600 x 900 resolution. After using the laptop, however, I can safely say that this resolution works fine. It is very sharp and bright and watching full screen movies is an amazing experience. I am really pleased with the display, both for its brightness and viewing angles. While it is a glossy (not a matte) display, it is bright enough that I can use it quite well even in bright, outdoor conditions. While the screen is 13.3” there is significant frame around the edge, unlike the ThinkPad X1 Carbon Touch which manages to fit a larger screen in the same size space by bringing it to the edges. This is not an issue in my mind because it makes it easy to use gestures and swipe from the side of the screen. The screen does take to fingerprints easily, but a small cloth takes care of that (and again, the brightness makes it difficult to notice).

The touch works flawlessly and handles all swipes and multi-gestures without issue. The only thing it is lacking is a digitizer pen. I feel like these should be standard with touch screens because they offer so much added precision and flexibility. Speaking of flexibility, the whole point of the Yoga is the various screen orientations that are available and it works well. While I won’t be using it in full laptop mode often, flipping the screen over in “display mode” (imagine the keyboard facing down and the screen closest to you rather than farther away as it is in the traditional laptop mode) is perfect for playing touch games while it’s resting on my lap or resting on my chest when I want to lie down and watch movies.

Sound

I’m more of a headphones person so I’ve never paid much attention to laptop speakers unless I’m bringing it outdoors to provide some entertainment. The speakers are housed beneath the keyboard and have decent volume. There is obviously no throbbing bass due to the ultrabook form factor but the volume and clarity are enough to make this work to play some tunes on the deck or while I’m packing my bags for a trip. The laptop comes with Dolby Home Theater and an impressive equalizer that I couldn’t tell you I know how to use.

I mentioned earlier I would share more about the Bluetooth. After an exhaustive search I decided I wanted to invest in a Bluetooth headset (more on the model, etc. below). The headset connected immediately and without issue to my Yoga and produces amazing quality sound. I haven’t tried it for a Skype call yet so the jury is out on the microphone, but I’m used to the older Bluetooth that was tinny and distorted. I’m getting a full range of CD quality sound that is fantastic. I love the freedom of listening without having to keep track of an audio cord.

Performance

General performance is fantastic. It will definitely serve as my primary development machine. I’ll talk a bit more about extensibility later, but I do two primary tasks during the day: writing and developing. The laptop performs superbly for both. My current configuration uses a docking station (more on  that later) to extend to two additional monitors, giving me a setup with three monitors total (the laptop 1600 x 900 and two 21” 1920 x 1080). I often have a web application on one screen, Visual Studio on another screen, and email or a team chat window open in the third window. I’m debugging a large enterprise project over a Cisco VPN over wireless and it all works flawlessly. Compile times are about 33% faster than my old Dell workstation (again, I attribute this mainly to the speed of the SSD drive) and everything just feels snappy. I haven’t figured out why yet (maybe there are more programs I’ve yet to install) but in general the Yoga seems to consistently use less memory than my Dell setup. Again, it could be some background agents that aren’t running yet.

I read some complaints about the WiFi not being dual band. I’m sure down the road I might have an issue with that but for now the bands included more than match the speed of my connections. My recent test gave me 28 Mbps which exceeds the advertised pipe for my Comcast broadband. No issues there!

Camera

The built-in camera is perfect for video conferences. I lead a large distributed development team and we frequently conduct virtual meetings using WebEx, GoToMeeting or even Google Hangouts. The team reported that the camera looked great and the built-in microphone was perfect. I made several Skype calls and had no complaints about audio fidelity. The following was taken during my flight with the screen shaking somewhat – note I’m leaning into the aisle to try to avoid bumping elbows with my seat mate.

picture005

Battery Life

The huge brick of a Dell I am used to would be lucky to get about 2 hours of solid battery life even with a bulky battery. My ASUS VivoTab RT, on the other hand, seems to go on forever – even on international flights I’ve never come close to running the battery out. While the Yoga may not be comparable to some laptops that approach the 10 hour range, the battery life is still impressive. I’ve had two tests so far – one was at my daughter’s archery competition when I was working on code samples and writing between her sets, and the second is writing this on a flight from Atlanta to Seattle. In the latter case I was editing the blog post, downloading software and listening to music over Bluetooth for the duration of the flight. Neither time did I completely drain the battery, but it showed plenty time remaining even after several hours of use at full brightness. It looks like I am on track to get a full five hours of use before recharging, and I’m assuming that will dwindle if I’m doing things like watching Netflix full screen. Overall I’m perfectly satisfied with the battery life because seldom will I be in a situation that requires it last longer.

Software

Although there were some complaints about initial configurations losing up to 40GB of storage, Lenovo seems to have addressed these and ships the laptop with only about 20GB taken up for recovery. That may seem like a lot but on the 256GB system I didn’t even bother with re-partitioning. I can always do that later if I need the space but for now I’m fine with having the little bit of recovery media available in case I need to reset.

Otherwise the shipped software was fairly light. I immediately uninstalled the McCaffee anti-virus as I find the built-in Microsoft tools to be sufficient. So far I’m tolerating the YouCam software, it seems to be a lot of bells and whistles around the built-in camera. Otherwise there is Kindle that holds the current copy of Steve Jobs’ biography that I’m reading, Evernote, a Tetris-like game that features birds, and some Lenovo software like gesture support for the cam and general support for the laptop. Windows 8 makes it extremely easy to simply tap and uninstall unwanted software, while some of the other programs need to be removed from the control panel.

Extensibility

One reason I used to always get Dell machines was due to the docking station that made it easy to dock and connect to multiple peripherals including a multi-monitor setup. This used to be something you had to give up when moving to ultraportable devices. Not any more! There are quite a few options that make it easy to extend the setup.The first thing I invested in was a Targus USB 3.0 Docking Station. The reviews seemed to go either way for this so I decided to take a chance. I am glad I did. I literally unboxed the Targus and plugged it into the Lenovo and it was recognized instantly – the drivers installed automatically in seconds. I was able to plug in my external USB 3.0 hard drive and set up File History. I then connected my two external monitors. The docking station has one DVI and one HDMI. I have two DVI cables. No worries, the docking station includes all of the converters you could hope for so I simply used the HDMI port with a DVI converter and was instantly greeted with three screens, the vivid Yoga at 1600x900 and my two 21” at 1920 x 1080 (when I return from the MVP summit I plan to connect one of them directly to the Yoga’s HDMI). The USB charging port was able to quickly charge my Sennheiser MM 450 headphones (that’s another review – I love them). Now docking my Lenovo is as simple as plugging a cable into the USB 3.0 port.

Here is the three-screen setup:

threescreens

The other accessory I got was for the road. With all of the coding and writing I do, I’ve come to rely on a dual-monitor set-up. Before I’d travel with my brick of a Dell along with a huge external keyboard and still be stuck with a single screen. Now with the lighter main laptop I was able to invest in a Lenovo ThinkVision 14” mobile monitor. This is a thin monitor that has a hard plastic cover to slide over and protect the glass and runs solely from USB power (there is an included Y-cable for situations when your ports are underpowered). Once again I plugged this in and instantly the driver was recognized and installed. The monitor is only 1366 x 768 and not very bright on a single USB port but it does double the number of screens and give me the workable space I need (it’s perfect for a Word document set to fit the display width when I have Visual Studio pulled up on the higher resolution laptop screen). It takes the place of my external keyboard in my laptop bag (no longer needed because the Yoga keyboard is so great) and ironically takes up less space. If you are interested in this option you may wish to wait; Lenovo is coming out with a 1600 x 900 model later this year with a touch and digitizer pen option.

Conclusion

So far I’m extremely pleased with the investment. I am still curious about some of the other devices coming down the pipe in 2013 but the Yoga has exceeded my expectations. It is light, thin, portable, yet powerful enough for me to conduct all of my daily tasks faster than I did on my previous workhorse laptop. That one, by the way, is going to get an installation of Plex and become the family media server.

Have you recently purchased a Windows 8 device? What device was it and why did you pick that particular model? How do you like it? Please leave your feedback in the comments section below and feel free to ask any questions you may have regarding the Yoga. Thanks!