Hello there fellow developers!
I am working on a Windows Phone 7 app and I can't figure out what I believe is a simple problem for the more seasoned ones.
Say I have a layout consisting of two elements: a ListBox (filled with an abundance of items) and a TextBlock (providing the user with some basic instructions).
I want these to be one above the other when the device is in Portrait orientation and I want these to be next to each other when the device orientation changes to Landscape.
For the Portrait orientation I am using a Grid layout manager, as it lets me define the rows' heights like so... row 0 Height="2*", row 1 Height="*"
Listbox sits in row 0, TextBlock in row 1. Now, what would be really neat is to simple change the RowDefinitions into ColumnDefinitions and reassign the listbox/textblock to the grid's columns instead of rows for when the device switches into Landscape.
But that's just my idea. I don't know how to get this done elegantly. Maybe there's a better approach to this? Or maybe this is the correct approach and there is some method built for exactly this purpose?
Thank you for your suggestions!
How about this for the default portrait configuration:-
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
</Grid.RowDefintions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="ItemList" Grid.ColumnSpan="2" />
<TextBlock x:Name="Instructions" Grid.Row="1" Grid.ColumnSpan="2">
Content
</TextBlock>
Then in your OrientationChanged event use:-
if ((e.Orientation & PageOrientation.Portrait) == PageOrientation.Portrait)
{
Grid.SetRow(ItemList, 0);
Grid.SetRowSpan(ItemList, 1);
Grid.SetColumn(ItemList, 0);
Grid.SetColumnSpan(ItemList, 2);
Grid.SetRow(Instructions, 1);
Grid.SetRowSpan(Instructions, 1);
Grid.SetColumn(Instructions, 0);
Grid.SetColumnSpan(Instructions, 2);
}
else
{
Grid.SetRow(ItemList, 0);
Grid.SetRowSpan(ItemList, 2);
Grid.SetColumn(ItemList, 0);
Grid.SetColumnSpan(ItemList, 1);
Grid.SetRow(Instructions, 0);
Grid.SetRowSpan(Instructions, 2);
Grid.SetColumn(Instructions, 1);
Grid.SetColumnSpan(Instructions, 1);
}
For orientation, Visual State Manager works the best.
In Blend, define two states, name them "port" and "land".
Put the "Device" control panel on the Blend workspace.
Record the layouts by switching orientation and designing each layout accordingly.
In the orientation change event, use the following code:
Code:
private void PhoneApplicationPage_OrientationChanged
(object sender, OrientationChangedEventArgs e)
{
VisualStateManager
.GoToState(this, e.Orientation.ToString().Substring(0, 4), true);
}
I found a nice article in msdn blogs that deals with this sort of layout transformation in a rather straightforward way and explains other approaches as well:
http://blogs.msdn.com/b/ptorr/archive/2010/03/27/strategies-for-dealing-with-orientation-changes.aspx
Why didn't I come across this earlier? :-) Happy coding!
Related
please how could I change the position of the elements in the UI, or choose a different design when the application loads?
It could be done using User Controls for each design, but the bad thing about this solution is that the same code will be repeated and I do not want that.
Please what would be the best practices to achieve this, it should be noted that the controls must have a name to use it in the code.
Thanks in advance.
Summary: This is what I want to achieve
In WPF the layout is all XAML. XAML can be stored in a resource dictionary. So once you determine what layout you want you can load the correct resource dictionary. Basically this pattern is exactly like people loading themes to change the colors of a UI etc.
I once had a situation similar to yours.
In your case, you should design your grid first as follows,
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding LeftButtonColWidth}"/>
<ColumnDefinition Width="{Binding RightMainPanelColWidth}"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height = "32"/>
<RowDefinition Height = "*"/>
<RowDefinition Height = "{Binding ightMainPanelBottomButtonRowHeight}"/>
</Grid.RowDefinitions>
Then add properties "LeftButtonColWidth", "RightMainPanelColWidth" and "RightMainPanelBottomButtonRowHeight" in viewmodel to control your layout based on some setting specified by end-users somewhere.
The above code is just for main container.
You also need a container grid for Buttons which should be designed as the main container grid using Binding property. In button container, you need bind Grid.Row and Grid.Column to properties "ButtonContainerRow" and "ButtonContainerCol" in ViewModel, they will be changed based on the some specific setting, when the app starts.
This is my solution. There must be other better solutions.
I hope someone can give me a solution only using xmal. That would be the perfect one.
Hi I thought I could solve this easily but it is driving me crazy.
I am using a UserControl to house a video player control based on VLC, along with play and stop buttons etc. I then place the UserControl on my main form. if the UserControl is declared in XAML it behaves normally.
I decided to rewrite the code to instantiate my UserControl dynamically, in case I need to destroy it and create another on the fly. But when I do the video moves to the top of its container instead of the middle.
The UserControl relevant section is here:
<Grid x:Name="LayoutParent" >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="12" />
</Grid.RowDefinitions>
<!-- I comment this if adding player dynamically -->
<!--<wpf:VlcPlayer Grid.Row="0" x:Name="Player" />-->
<!-- I un-comment this if adding player dynamically -->
<Grid x:Name="VideoPlayerPanel" Grid.Row="0" Margin="0" />
<StackPanel Grid.Row="1" Opacity="0.8">
...(buttons etc)
</StackPanel>
<ProgressBar ...(progressBar etc) />
</Grid>
My codebehind looks like this:
Dim Player As VlcPlayer = New VlcPlayer ' uncomment If adding the player dynamically
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Player.SetValue(Canvas.ZIndexProperty, -1)
VideoPlayerPanel.Children.Add(Player)
VolumeSlider.Value = 50
End Sub
I have tried VerticalAlignment="Center" and VerticalAlignment="Stretch" in XAML on the VideoPlayerPanel, with Center the video disappears entirely, with Stretch it still aligns to the top.
Any thoughts as to what I might do to align this centrally would be much appreciated!
When adding Player dynamiccaly you have different result, because you wrap Play in additional Grid. Try to add Player directly to first row of LayoutParent:
Player.SetValue(Grid.Row, 0)
LayoutParent.Children.Add(Player)
Thanks to all that replied.
I did some more research, I substituted in a Rectangle for the player control and it aligned perfectly. That led me to discover that the third party control was itself at fault. I had to get the source and change the VerticalAlignment directly.
Sorry for the runaround.
Remove Height="*" from first Row . * is used to occupy remaining space, so it is good to use it for the last Row.
Use fixed width and or Auto.
I have a Silverlight page that I would like to have below appearance in portrait and landscape mode. Essentially, there are three images arranged in a grid. The big image spans two columns. When the phone rotates, the images rotate, but the overall layout does not. The small images remain close to the back/windows/search button and the large image remains towards the top of the phone.
I have tried a number of methods to achieve this effect, but they have all proved unsatisfactory in one way or another. I'm hoping that someone can point out something that I'm missing or, at the very least, prevent someone else from having to waste 4 or 5 days coming to the same conclusion that I did. Questions in Bold:
The first thing I tried was applying a RotateTransform to the LayoutRoot element and rotating it -90 degrees whenever the phone rotation changed to landscape. I had to hard code the height and width of the layout root to 800 and 400 instead of "Auto" or it gets drawn squished. This solution almost worked, but the RotateTransform gets applied after the page is drawn. Because it is drawn as an 400x800 image on an 800x400 screen, the top 200 and bottom 200 pixels aren't drawn. This becomes obvious after it's rotated and the (now)left and right portions are missing. Is there a way to force the layout engine to draw off the screen so that all the pixels are there after the RotateTransform is applied?
The next thing I considered (but did not try) was to set the page SupportedOrientations to "PortraitOnly" and then use the accelerometer to generate my own "OnOrientationChanged" event and then selectively rotate the images by 90 degrees when the phone is tilted to landscape. I determined this is a bad idea because I would probably get this wrong in some subtle way resulting in confusion when rotation didn't work quite the same way in my app as in every other app. Is there a way to have the OnOrientationChanged event fire without also automatically updating the layout of the grid that contains my elements? Alternatively, is there some other hook that can be used to detect the phone orientation?
The last thing I tried was similar to the advice offered here: Windows Phone 7 applications - Orientation Change and here: http://blogs.msdn.com/b/ptorr/archive/2010/03/27/strategies-for-dealing-with-orientation-changes.aspx. This solution seems a bit brittle to me because it forces me to change the relative sizes of my grid rows and columns in the OnOrientationChanged event handler as well as in the xaml code. In the portrait mode, the first row is set to 5* and the 2nd row is set to 2*. Then when I switch to landscape, the rows need to be each set to 1* and the columns need to be set to 5* and 2* respectively. Alternatively, I could hard-code the size of the small images and set the rows and columns to Auto, but then I'm still stuck hard coding something. Since I've exhausted all of my other options, I think this is the solution that I'm stuck with.
Am I missing anything, or is this the way to do it?
You don't really have to hard code anything. What you need is clever designing. To develop good applications with multiple orientation support you should come up with a clever grid layout which lets you reposition objects the way you want without creating a mess.
For your situation consider the layout:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3.5*" />
<ColumnDefinition Width="1.5*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2.5*"/>
<RowDefinition Height="1.5*" />
<RowDefinition Height="1.5*" />
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Margin="12,17,0,28" Grid.ColumnSpan="3" Grid.RowSpan="3"></StackPanel>
<Image Name="bigSmiley" Margin="8" Grid.RowSpan="3" Grid.ColumnSpan="3" Source="big.jpg"
Stretch="Fill"/>
<Image Name="smallSmiley1" Grid.Row="3" Source="smiley.jpg" Stretch="Fill" Margin="8"/>
<Image Name="smallSmiley2" Grid.Row="3" Grid.Column="1"
Source="smiley.jpg" Stretch="Fill" Margin="8"
Grid.ColumnSpan="2" />
<!--ContentPanel - place additional content here-->
</Grid>
And your orientation changed method should be like:
private void PhoneApplicationPage_OrientationChanged(object sender, OrientationChangedEventArgs e)
{
if ((e.Orientation & PageOrientation.Portrait) == PageOrientation.Portrait)
{
Grid.SetRow(bigSmiley, 0);
Grid.SetColumn(bigSmiley, 0);
Grid.SetColumnSpan(bigSmiley, 3);
Grid.SetRowSpan(bigSmiley, 3);
Grid.SetRow(smallSmiley1, 3);
Grid.SetColumn(smallSmiley1, 0);
Grid.SetColumnSpan(smallSmiley1, 1);
Grid.SetRowSpan(smallSmiley1, 1);
Grid.SetRow(smallSmiley2, 3);
Grid.SetColumn(smallSmiley2, 1);
Grid.SetColumnSpan(smallSmiley2, 2);
Grid.SetRowSpan(smallSmiley2, 1);
}
else
{
Grid.SetRow(bigSmiley, 0);
Grid.SetColumn(bigSmiley, 0);
Grid.SetColumnSpan(bigSmiley, 2);
Grid.SetRowSpan(bigSmiley, 4);
Grid.SetRow(smallSmiley1, 0);
Grid.SetColumn(smallSmiley1, 2);
Grid.SetColumnSpan(smallSmiley1, 1);
Grid.SetRowSpan(smallSmiley1, 2);
Grid.SetRow(smallSmiley2, 2);
Grid.SetColumn(smallSmiley2, 2);
Grid.SetColumnSpan(smallSmiley2, 1);
Grid.SetRowSpan(smallSmiley2, 2);
}
}
Result:
I hope that solves your issue. Remember, there's always a better design which makes your problems go away! :)
I have a relatively complex layout. It consists of:
A grid with one column and three rows.
In the first row (the on giving me trouble) I have a developer express componenet - another GridControl.
My problem is, that though the height of this first row is Auto, the vertical scrollbar displays even though there's space enough for content.
I've tried setting the ScrollViewer.VerticalScrollBarVisibility="Hidden" on the row's rowdefinition, but this doesn't help.
Likewise, I've set the inner GridControl to not use scrollbars (using some Developer Express magic - not just ScrollViewer as this doesn't work)
Yet, no matter what I do, that damn scrollbar appears... Is there any way to figure out which control renders it, so I can disable the damn thing? It's not just a question of it being ugly - scrolling it actually messes with the layout!
Thanks in advance!
The relevant code:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" ScrollViewer.VerticalScrollBarVisibility="Hidden" />
<RowDefinition Height="*" MaxHeight="240" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<dxg:GridControl Name="StudySizeGrid" Grid.Column="0" Grid.Row="0" >
<dxg:GridControl.Resources>
<ControlTemplate x:Key="{dxgt:TableViewThemeKey ResourceKey=ControlTemplate}">
<ScrollViewer x:Name="scr"
VerticalScrollBarVisibility="Disabled"
HorizontalScrollBarVisibility="Disabled"
Focusable="False"
dxg:GridControl.CurrentView="{Binding RelativeSource={RelativeSource TemplatedParent}}"
Template="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=ScrollViewerTemplate}}">
<ScrollViewer.CanContentScroll>False</ScrollViewer.CanContentScroll>
</ScrollViewer>
</ControlTemplate>
</dxg:GridControl.Resources>
...
</dxg:GridControl>
EDIT FOR CLARIFICATION: This is WPF issue :-)
You could try checking out the VisualTree, i think Snoop might be helpful for that, it probably has some other useful features too. Getting the VisualTree is a trivial matter though, you can write a single recursive method using the VisualTreeHelper, so you might not need the big guns.
e.g.
public static TreeViewItem GetVisualTree(this DependencyObject dpo)
{
TreeViewItem item = new TreeViewItem();
item.Header = dpo.GetType().ToString().Split('.').Last();
if (dpo is FrameworkElement && (dpo as FrameworkElement).Name != string.Empty) item.Header += " (" + (dpo as FrameworkElement).Name + ")";
int cCount = VisualTreeHelper.GetChildrenCount(dpo);
for (int i = 0; i < cCount; i++)
{
item.Items.Add(VisualTreeHelper.GetChild(dpo, i).GetVisualTree());
}
return item;
}
Wrote that quite some time ago, it's very sketchy (wouldn't recommend making it an extension method), gets the whole tree at one, could be modified to only fetch children on expansion of the node.
You could use something like Google Chrome's tools.
I would, in Chrome, right click around the area that has the scroll bars and select "Inspect Element". Chrome will highlight with a border what element you are looking at. You can then navigate the html within Google Chrome's inspector until it is highlighting the element with the scrollbar.
You can then find the reason from there.
I'm trying to do something which seems like it should be extremely simple and yet I can't see how. I have a very simple layout, a TextBox with an image next to it, similar to the way it might look adorned with an ErrorProvider in a WinForms application. The problem is, I want the image to be no higher than the TextBox it's next to. If I lay it out like this, say:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" MinWidth="100"/>
<Image Grid.Row="0" Grid.Column="1" Source="error.png" />
</Grid>
the row will size to the height of the image if the image is taller than the TextBox. This also happens if I use a DockPanel or StackPanel.
The naive solution would be to bind the Height to the TextBox's ActualHeight. I'm sure this is wrong. But what's right?
Edit
Here's an example of what looks wrong to me: In both of these layouts (which are both horizontal StackPanels), the FontSize is the only variable:
You can see that the first TextBox is constrained to the height of the icon, and as a result has an unnecessary bottom padding under the text. And the icon next to the second is out of scale to the TextBox it's next to.
As it happens, I found a completely different (and much better) way to approach the problem - originally I was scaling my layout by changing the FontSize on the Window, but using a ScaleTransform is a whole lot easier and seems to work perfectly. But even so, it still seems odd to me that it's so hard to do this.
Name your TextBox, reference the TextBox from the Image as follows.
<TextBox Name="myTextBox"
Grid.Row="0" Grid.Column="0"
MinWidth="100"/>
<Image Grid.Row="0"
Grid.Column="1"
Source="error.png"
Height="{Binding ActualHeight, ElementName=myTextBox}"/>
You want a layout algorithm that measures the other elements with a height constraint equal to the desired height of a specific one. While several of the existing Panel implementations will reduce the available space for remaining elements based on the size used by previous ones, none of them will set the constraint the way you want. If you want the behavior you describe in a single layout pass, you will need to write your own layout algorithm.
For example, you can get close by overriding the behavior of StackPanel like this:
public class SizeToFirstChildStackPanel
: StackPanel
{
protected override Size MeasureOverride(Size constraint)
{
if (Children.Count > 0)
{
var firstChild = Children[0];
firstChild.Measure(constraint);
if (Orientation == Orientation.Horizontal)
{
constraint = new Size(
constraint.Width,
Math.Min(firstChild.DesiredSize.Height, constraint.Height));
}
else
{
constraint = new Size(
Math.Min(firstChild.DesiredSize.Width, constraint.Width),
constraint.Height);
}
}
return base.MeasureOverride(constraint);
}
}
This will constrain the height of all children of the StackPanel to the desired height of the first one (or width if the panel is oriented vertically).
It's still not ideal because the first child will get measured a second time by the base class MeasureOverride, and the second measure will have a constraint. This is extra work, and will cause odd behavior if the first child wants to get larger.
To do it right you would need to implement the entire MeasureOverride method method yourself. I'm not going to do that here because it would be a lot of code that isn't really relevant to the problem, and it depends on how exactly you want the layout to work. The important point is to measure the specific element first and use its DesiredSize to determine the availableSize when you call Measure on the others.