How to do screen-size-aware layout? - windows-8

I'm making a board game. To construct the board, I do the following:
// adapted from http://code.msdn.microsoft.com/windowsapps/Reversi-XAMLC-sample-board-816140fa/sourcecode?fileId=69011&pathId=706708707
// This is shit code.
async void PlayGame_Loaded(object sender, RoutedEventArgs e)
{
foreach (var row in Game.ALL_ROWS)
{
boardGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(BOARD_GRID_SIDE_LENGTH) });
}
var maxColIndex = Game.ALL_ROWS.Max();
foreach (var col in Enumerable.Range(0, maxColIndex))
{
boardGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(BOARD_GRID_SIDE_LENGTH) });
}
// ...
}
(Feel free to suggest alternate approaches.)
Basically, I create a bunch of rows and columns based on a pre-set height and width, and fill those rows and columns with board spaces. This works fine when I set the row and column lengths to fit my laptop, but obviously it won't work on devices with different resolutions. (For instance, it's truncated on the Surface RT.) How do I get around this? Can I specify a side length that's a portion of the parent container? What's the best practice here?

Your best bet would probably be to use a ViewBox, assuming the board you want to draw has a constant number of rows/columns.

I use this:
var bounds = Window.Current.Bounds;
double height = bounds.Height;
double width = bounds.Width;
to find out the screen resolution on which my apps are, and then re-size my grid items on function of the size of the screen.
EDIT:
So for your code I would do:
async void PlayGame_Loaded(object sender, RoutedEventArgs e)
{
var bounds = Window.Current.Bounds;
double BOARD_GRID_SIDE_LENGTH = bounds.Height;
double BOARD_GRID_SIDE_WIDTH = bounds.Width;
foreach (var row in Game.ALL_ROWS)
{
boardGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(BOARD_GRID_SIDE_LENGTH) });
}
var maxColIndex = Game.ALL_ROWS.Max();
foreach (var col in Enumerable.Range(0, maxColIndex))
{
boardGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(BOARD_GRID_SIDE_WIDTH) });
}
// ...
}

The sound solution is to divide your page so that the boardGrid takes 2/3 of the screen, the rest of your content (buttons, timer, score board...) in the other third.
Secondly, enclose it inside a Viewbox element so that the board dimensions are recalculated when you go to different view modes (Full, Portrait, Snapped, Filled)
Finally, make sure to make the page inherit LayoutAwarePage instead of Page so that it handles the changes of view modes gracefully
Here's a rough mockup:
<common:LayoutAwarePage
xmlns:UI="using:Microsoft.Advertising.WinRT.UI"
x:Class="ApexKT.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp"
xmlns:common="using:MyApp.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:snaps="http://schemas.microsoft.com/netfx/2007/xaml/presentation"
mc:Ignorable="d" >
<Grid x:Name="bigGrid">
<StackPanel Orientation="Horizontal">
<StackPanel x:Name="OneThird">
<TextBlock x:Name="ScoreBoard" Text="Score:" />
<Button x:Name="Button1" Content="Button 1" Click="" />
</StackPanel>
<Viewbox x:Name="TwoThirds">
<Viewbox.Transitions>
<TransitionCollection>
<ContentThemeTransition />
</TransitionCollection>
</Viewbox.Transitions>
<Grid x:Name="boardGrid"><!-- insert row and column definitions here, or hard code them --></Grid>
</Viewbox>
</StackPanel>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled"/>
<VisualState x:Name="Snapped" />
<VisualState x:Name="FullScreenPortrait" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
`

Related

UWP: how to get element size before painting

My code will draw a graphic and, before the paint event, I need to set the size of element containing the graphic. In part, this comes from a value in an XAML file:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="35" />
</Grid.RowDefinitions>
...
</Grid>
During the view initialization, I'm hoping to be able to modify the graphic width based on some other factors, but I need the height value, from XAML.
At a breakpoint, I can view the various View values, and at this point ActualHeight and ActualWidth are still 0. I don't see anything else I could use.
Is there another event, coming before paint, that I could use ?
The answer is to use SizeChanged event.
In XAML, for example:
<skia:SKXamlCanvas
x:Name="EICanvas"
SizeChanged="OnSizeChanged" />
And in code-behind:
private void OnSizeChanged (Object sender, SizeChangedEventArgs e)
{
var newH = e.NewSize.Height;
var oldH = this.ActualHeight; // in pixels
...
}

How to make sure a Popup control match its parent Page when the parent is resized? UWP

I have a Popup which will fill the whole page when opened.
<Grid x:Name="gridRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="Open" HorizontalAlignment="Center" Click="{x:Bind viewModel.OpenPopup}" />
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsOpen}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:MyPopup Width="{Binding ElementName=gridRoot, Path=ActualWidth}" Height="{Binding ElementName=gridRoot, Path=ActualHeight}"/>
</Popup>
</Grid>
The Popup is a UserControl
<Grid Background="Red">
<Button Content="Close" HorizontalAlignment="Center" Click="{x:Bind viewModel.ClosePopup}" />
</Grid>
The page
When popup is shown
Close the popup, resize the page, then reopen the popup. Notice that it does not match the new size of container page even though its Width and Height is bound to gridRoot . Do I have to manually set a new Width and Height for the popup? Why can't I achieve this with binding? This issue also appears on mobile during 'OrientationChanged'
Based on Decade Moon comment, this is how to resize the popup to match the parent container as its size changed.
Create a dependency property in the code behind
public double PageWidth
{
get { return (double)GetValue(PageWidthProperty); }
set { SetValue(PageWidthProperty, value); }
}
public static readonly DependencyProperty PageWidthProperty =
DependencyProperty.Register("PageWidth", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
public double PageHeight
{
get { return (double)GetValue(PageHeightProperty); }
set { SetValue(PageHeightProperty, value); }
}
public static readonly DependencyProperty PageHeightProperty =
DependencyProperty.Register("PageHeight", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
Update the value on SizeChanged event
private void GamePage_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width > 0d && e.NewSize.Height > 0d)
{
PageWidth = e.NewSize.Width;
PageHeight = e.NewSize.Height;
}
}
Then in XAML, just use x:Bind to bind the popup width and height
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsPopupCorrectOpen, Mode=TwoWay}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:PopupCorrect Width="{x:Bind PageWidth, Mode=TwoWay}" Height="{x:Bind PageHeight, Mode=TwoWay}"/>
</Popup>
Pretty straight forward. Just remember not to use the ActualWidth or ActualHeight properties for binding source as they do not raise the PropertyChanged event.
Although it has an ActualWidthProperty backing field, ActualWidth does not raise property change notifications and it should be thought of as a regular CLR property and not a dependency property.
For purposes of ElementName binding, ActualWidth does not post updates when it changes (due to its asynchronous and run-time calculated nature). Do not attempt to use ActualWidth as a binding source for an ElementName binding. If you have a scenario that requires updates based on ActualWidth, use a SizeChanged handler.
#PutraKg have a great way.
But I have two way to solve it.
The first is set the VerticalAlignment="Center" HorizontalAlignment="Center" that can make the popup in the center.
But I think youare not content to put it in the center.
The great way is use the screen position.
You can get the Grid's screen postion and make it to popup.
In open button
private void Button_OnClick(object sender, RoutedEventArgs e)
{
var grid = (UIElement)popupCorrect.Parent; //get grid
var p = grid.TransformToVisual (Window.Current.Content).TransformPoint(new Point(0, 0)); //get point
popupCorrect.HorizontalOffset = p.X;
popupCorrect.VerticalOffset = p.Y;
popupCorrect.IsOpen = !popupCorrect.IsOpen;
}

PrepareContainerForItemOverride works different in Desktop than in Mobile UWP

I wanted to change the color of an item of ListView according the data value.
It would be easy doing:
<ListView.ItemContainerStyle>
<Style TargetType = "ListViewItem" >
< Setter Property="Background" Value="{Binding EventType, Converter={StaticResource EventTypeToBackColorConverter}}" />
</ListView.ItemContainerStyle>
But the thing is that UWP does not support binding in Setter Properties.
My second attempt was overriding PrepareContainerForItemOverride of the ListView:
public class EventListView : ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var listViewItem = element as ListViewItem;
var ev = item as EventType;
if(ev.Warning)
listViewItem.Background = new SolidColorBrush(Color.Red);
}
}
The above code works fine running in a PC with Windows 10 and UWP. It colors in red some items according the underlying data. When I run the same app in Windows Mobile, at beginning it works fine, but when I scroll up and then I scroll down, returning to the original view that was ok at beginning, now other items are also in red color.
What I am missing?
I am not sure the reason, but the following code works for me:
public class EventListView : ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var listViewItem = element as ListViewItem;
var ev = item as EventType;
if(ev.Warning)
listViewItem.Background = new SolidColorBrush(Color.Red);
else
listViewItem.Background = null;
}
}
I have added listViewItem.Background = null
This is because when there are a large number of Items, by default ListView has implement the function of data virtualization. It's not a good idea to disable this function since it can achieve a better performance.
But for your scenario, there is a much easier method to solve your problem. Since you're trying to modify the style of ListViewItem in the code behind, and we can't modify the existed one, we can set a new style of ListViewItem to ListView for example like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
var dynamicStyle = new Style();
dynamicStyle.TargetType = typeof(ListViewItem);
dynamicStyle.Setters.Add(new Setter(BackgroundProperty, Colors.Red));
listView.ItemContainerStyle = dynamicStyle;
}
Only one problem is, if you are setting the Background property to all the ListViewItem, it makes no difference than binding data to the Background property of ListView or setting the Background to ListView like this:
listView.Background = new SolidColorBrush(Colors.Red);
So I just assume that you want to modify the root control in the DataTemplate for example like the Grid in the following xaml:
<ListView x:Name="listView" ItemsSource="{x:Bind collection}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" x:Name="myListItemStyle">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Testtext}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Then in this scenario, you can use data binding probably like this:
<DataTemplate>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{Binding EventType, Converter={StaticResource EventTypeToBackColorConverter}}">
<TextBlock Text="{Binding Testtext}" />
</Grid>
</DataTemplate>
Any way, if you insist to change some property of all ListViewItem in the ListView, you can use the first method I provided.

RenderTargetBitmap messes up the WebView scaling

I have a very simple repro case of RenderTargetBitmap.RenderAsync overload messing up the WebView scaling. All I have on the MainPage is a WebView and a button:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<WebView Source="http://bing.com"></WebView>
<Button Content="Render me"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Click="ButtonBase_OnClick" />
</Grid>
In code behind there's only a simple event handler
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
RenderTargetBitmap rtb = new RenderTargetBitmap();
await rtb.RenderAsync(this, 1280, 720);
}
This is what the page looks like before RenderAsync call:
and this is what happens after the call:
Any idea why and how to prevent this? Note that it only happens if I call
await rtb.RenderAsync(this, 1280, 720);
but NOT if I call the overload without the scaling
await rtb.RenderAsync(this);
EDIT: Due to the first answer I received, I wanted to clarify why the aspect ratio is not the problem here, but only serves the purpose of proving that there actually is a problem. Think of the following scenario - very high DPI screen where only a lower resolution screenshot is needed - even if you scale it down with the RIGHT ratio, it still messes up the WebView. Also, for my scenario, resizing the screenshot manually afterwards is not an option - the RenderAsync overload with scaled dimensions is much much faster and I would really prefer to use that method.
Very strange behavior...
I found one very dirty (!) fix to this. I basically hide and show the webview (wv) again.
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
RenderTargetBitmap rtb = new RenderTargetBitmap();
await rtb.RenderAsync(wv, 1280, 720);
wv.Visibility = Visibility.Collapsed;
await Task.Delay(100);
wv.Visibility = Visibility.Visible;
}
I'm not proud of this solution and the webview flashes, but at least it's not 'blown up' any more...
This is a bit of a hack too, but I found that if you set the contents of another control through a WebViewBrush and then render that control, then the source WebView doesn't get any scaling. I have modified the XAML you provided so it looks like this:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border x:Name="Target" Width="1280" Height="720" />
<WebView x:Name="webView" Source="http://bing.com" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></WebView>
<Button Content="Render me" Grid.Row="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Click="ButtonBase_OnClick" />
</Grid>
In your case, you should opt to set the Border control behind your WebView (however, don't change its Visibility or put it outside of the window bounds, as RenderAsync will fail). Then, on the code behind, set the Background of the target control to an instance of a WebViewBrush that feeds on the WebView:
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
WebViewBrush brush = new WebViewBrush();
brush.SetSource(webView);
Target.Background = brush;
Target.InvalidateMeasure();
Target.InvalidateArrange();
RenderTargetBitmap rtb = new RenderTargetBitmap();
await rtb.RenderAsync(Target, 1280, 720);
var pixels = await rtb.GetPixelsAsync();
}
You will get your final image without any issues caused to the source WebView (however, note that the final image will look distorted since the aspect ratios don't match). However this comes with a few caveats, the most important one being that the WebView size must match the one of the RenderTargetBitmap or you will get empty areas.
Instead of using fixed values, use VisibleBounds to get the current window size.
Here's the code:
private async void pressMe_Click(object sender, RoutedEventArgs e)
{
var windowBounds = ApplicationView.GetForCurrentView().VisibleBounds;
RenderTargetBitmap rtb = new RenderTargetBitmap();
await rtb.RenderAsync(this, (int)windowBounds.Width, (int)windowBounds.Height);
}

Auto-complete box under a text box in Windows 8 / Metro UI

I want to implement auto-complete on a textbox in a Windows 8 UI / Metro UI app using C#/XAML.
At the moment, when the soft / touch keyboard shows, it obscures the auto-complete box. However, on the text box focus, Windows 8 automatically scrolls the entire view up and ensures the text box is in focus.
In reality, all I want is the view to scroll up a little more (in fact, by the height of the auto-complete box).
I realise I can intercept the Showing event of InputPane.GetForCurrentView()
I can set InputPaneVisibilityEventArgs.EnsuredFocusedElementInView to true inside the Showing event fine (so Windows won't try to do anything).... however, how can I invoke the same scrolling functionality that Windows 8 would do, but ask it to scroll a little more!?
Here's the code for the main page:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0,200,0,0">
<TextBlock HorizontalAlignment="Center" FontSize="60">App 1</TextBlock>
<TextBlock HorizontalAlignment="Center">Enter text below</TextBlock>
<TextBox HorizontalAlignment="Center" Margin="-10,0,10,0" Width="400" Height="30"/>
<ListBox HorizontalAlignment="Center" Width="400">
<ListBoxItem>Auto complete item 1</ListBoxItem>
<ListBoxItem>Auto complete item 2</ListBoxItem>
<ListBoxItem>Auto complete item 3</ListBoxItem>
<ListBoxItem>Auto complete item 4</ListBoxItem>
<ListBoxItem>Auto complete item 5</ListBoxItem>
</ListBox>
</StackPanel>
</Grid>
If you start up the simulator with the lowest resolution, use the hand to "touch" the textbox, this will bring up the soft keyboard. In the real app, the auto complete list will appear with items as the user enters text.
So in a nutshell, how can I move the screen up a bit more so the user can see the entire autocomplete list?
Bear in mind, in the real app, it'll be worse, as the user may not even notice the autocomplete list appearing "underneath" the keyboard.
I really would appreciate some advice, many thanks!
I have created an AutoCompleteBox for Windows Store apps, the nuget package is available at https://nuget.org/packages/AutoCompleteBoxWinRT
Ok, here is how I would tackle this since I cannot seem to find any way to control the scrolling of the app based on the appearance of the keyboard. I would create a user control that would form the basis for the auto-complete textbox.
<UserControl
x:Class="App6.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App6"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<TextBox x:Name="textBox" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" GotFocus="textBox_GotFocus" LostFocus="textBox_LostFocus" />
<ListBox x:Name="listBox" Height="150" Margin="0,-150,0,0" VerticalAlignment="Top" Visibility="Collapsed"/>
</Grid>
This is an incredibly basic implementation, so you will have to tweak to meet your needs.
Then, I would add the following code-behind to the user control
public sealed partial class MyUserControl1 : UserControl
{
// Rect occludedRect;
bool hasFocus = false;
public MyUserControl1()
{
this.InitializeComponent();
InputPane.GetForCurrentView().Showing += MyUserControl1_Showing;
}
void MyUserControl1_Showing(InputPane sender, InputPaneVisibilityEventArgs args)
{
if (hasFocus)
{
var occludedRect = args.OccludedRect;
var element = textBox.TransformToVisual(null);
var point = element.TransformPoint(new Point(0, 0));
if (occludedRect.Top < point.Y + textBox.ActualHeight + listBox.ActualHeight)
{
listBox.Margin = new Thickness(0, -listBox.ActualHeight, 0, 0); // Draw above
}
else
{
listBox.Margin = new Thickness(0, textBox.ActualHeight, 0, 0); // draw below
}
}
}
private void textBox_GotFocus(object sender, RoutedEventArgs e)
{
listBox.Visibility = Windows.UI.Xaml.Visibility.Visible;
hasFocus = true;
}
private void textBox_LostFocus(object sender, RoutedEventArgs e)
{
listBox.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
hasFocus = false;
}
}
Next steps would be to expose properties to pass data to be bound to the ListBox. Hard core would be ListBoxItem templating and more, depending on how reusable you wanted it to be.