Xamarin forms location of Grid or Button on screen - c#

I have been trying for a while to find the exact location of a grid and the button inside the grid I have on my screen. It is within many layers of Grids and StackLayouts, which have varying paddings.
I have tried using absolute layout, but it never puts the control i'm trying to draw over it in the correct place.
Grid g = new Grid;
g.BackGroundColor = Color.Pink;
g.WidthRequest = 48;
g.HeightRequest = 48;
g.VerticalOptions = LayoutOptions.Start;
g.HorizontalOptions= LayoutOptions.Start;
g.TranslationX = AbsoluteLayout.GetLayoutBounds(LeftButton).Location.X;
g.TranslationY = AbsoluteLayout.GetLayoutBounds(LeftButton).Location.Y;
PageSizedGrid.Children.Add(g);

Related

How to overlap WPF Images

Is there a way overlap say three images to have them overlap. I am have a case where I bring the top element to the top but it is showing the base layer underneath instead of the second.
Here is what I mean:
Should be this:
The code for it:
// Bottom Box
this.BottomBox.BackColor = Color.Transparent;
this.BottomBox.BackgroundImage = Resource.BottomBox;
this.BottomBox.Name = "BottomBox";
// Middle Box
this.Middle.BackColor = Color.Transparent;
this.Middle.BackgroundImage = Resource.MiddleBox;
this.Middle.Parent = BottomBox;
this.Middle.Name = "Middle";
// Top Box
this.TopBox.BackkColor = Color.Transparent;
this.TopBox.BackgroundImage = Resource.TopBox;
this.TopBox.Parent = MiddleBox;
this.TopBox.Name = "TopBox";
The easiest way to overlap controls/views (images, etc) in WPF is to put them inside a Canvas or a Grid...
In a Canvas, you can alter there relative positions using the attached properties Canvas.Top and Canvas.Left.
In a Grid, you can alter their relative positions using margins...
You can do this in the Constructor of the views immediately below the InitializeComponent method.
Sample Code:
// Bottom Box
this.BottomBox.BackColor = Color.Transparent;
this.BottomBox.BackgroundImage = Resource.BottomBox;
this.BottomBox.Name = "BottomBox";
// Middle Box
this.Middle.BackColor = Color.Transparent;
this.Middle.BackgroundImage = Resource.MiddleBox;
this.Middle.Parent = BottomBox;
this.Middle.Name = "Middle";
// Top Box
this.TopBox.BackkColor = Color.Transparent;
this.TopBox.BackgroundImage = Resource.TopBox;
this.TopBox.Parent = MiddleBox;
this.TopBox.Name = "TopBox";
var canvas = new Canvas();
canvas.Children.Add(this.BottomBox);
canvas.Children.Add(this.TopBox);
canvas.Children.Add(this.Middle);
Canvas.SetLeft(this.BottomBox, 50);
Canvas.SetTop(this.BottomBox, 100);
Canvas.SetLeft(this.Middle, 50);
Canvas.SetTop(this.Middle, 50);
Canvas.SetLeft(this.TopBox, 50);
Canvas.SetTop(this.TopBox, 0);
//put canvas as the main element to display in the view
Try it out and leave a comment if you have any further issue.

Textblock margin causes out of bounds text

I'm currently trying to create a visual component to have scrolling text (left to right and right to left) - pretty much an html marquee.
I have a grid divided in several columns & rows, and I want to place my component inside one of the grid slots.
The grid (named UIGrid) is generated like this :
for (int i = 0; i < xDivisions; i++)
{
ColumnDefinition newColumn = new ColumnDefinition();
UIGrid.ColumnDefinitions.Add(newColumn);
}
for (int i = 0; i < yDivisions; i++)
{
RowDefinition newRow = new RowDefinition();
UIGrid.RowDefinitions.Add(newRow);
}
The component I'm adding is just a border with a textblock as a child. I place the border inside the Grid like this :
border = new Border();
Grid.SetColumn(border, xPosition);
Grid.SetRow(border, yPosition);
textBlock = new TextBlock();
border.Child = textBlock;
textBlock.Text = "Scrolling text from left to right";
UIGrid.Children.Add(border);
I'm using a timer to increment the textblock margin, here's the timer callback simplified body :
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
double textWidth = textBlock.DesiredSize.Width;
double visibleWidth = componentBase.ActualWidth;
double targetMargin = textWidth < visibleWidth ? visibleWidth : textWidth;
if (margin.Left == targetMargin)
{
margin.Left = -textWidth;
} else
{
margin.Left++;
}
When the text slides from left to right, it behaves nicely :
https://s10.postimg.org/p0nt7vl09/text_good.png
Text "leaving" the grid slot is hidden.
However, when I set the textblock's margin as negative so it may come back inside the viewable area from the left, the text is visible even though it's outside its allocated slot :
https://s10.postimg.org/pownqtjq1/text_bad.png
I've tried using padding instead, but I can't set a negative padding. I've tried a few other things, but I feel like I've encountered a roadblock.
What could I do to get a nicely scrolling text ?
If you want nicely scrolling text ListView might be a better option. It is dynamic and you can bind it to your object. It would take a lot of this guess work out.
Ed Plunkett led me in the right direction with the Clip property. The idea is to do this :
border.Clip = new RectangleGeometry
{
Rect = new Rect(0, 0, border.ActualWidth, border.ActualHeight)
};
Of course, that doesn't work if the border hasn't been rendered yet (and of course it isn't when my code is running). You can force the measurement to take place using 'Measure' as I did to measure the text length in pixels, but it behaved strangely on my border. I wouldn't get the correct size at all.
In the end, I simply subscribed to the border's SizeChanged event :
border.SizeChanged += OnSizeComputed;
When that event is fired, I create the RectangleGeometry using ActualWidth & ActualHeight.

How to create a box inside a grid that contains an image and labels

How can I create a grid in which i can put a box like structure that contains an image and a label using xamarin.forms.
I am posting the image which shows what I need
"I" refers to the Image
"N" refers to the number
"W" refers to the text
There are two separate components here.
First is the creation of a custom layout you need for the image, number and text.
The second is how you then display that custom layout inside your page.
The Grid is certainly a possibility for the second component, but you really don't need it for the first one. Especially because nesting grids inside Xamarin.Forms thrashes layout performance.
So use either two stack layouts or a relative layout.
For the former
StackLayout box = new StackLayout {
Orientation = Vertical
};
StackLayout firstRow = new StackLayout {
Orientation = Horizontal
};
firstRow.Children.Add(new Image {Source="FileName.png"});
firstRow.Children.Add(new Label {Text="1", HorizontalOptions = LayoutOptions.End});
box.Children.Add(firstRow);
box.Children.Add(new Label { Text = "This is the text" });
Or for the latter, something like
RelativeLayout rl = new RelativeLayout();
var image = new Image {Source="FileName.png"};
var numberLabel = new Label {Text="1"};
var textLabel = new Label{Text="This is the text"};
rl.Children.Add(image,Constraint.Constant(0),Constraint.Constant(0)); //Add image at 0,0 = top left
rl.Children.Add(numberLabel,Constraint.RelativeToParrent(parent=>parent.Width - numberLabel.Width),Constraint.Constant(0)); // add numberLabel at the top right corner of the relative layout
rl.Children.Add(textLabel,Constraint.RelativeToView(image,(sibling,parent) => sibling.Y + sibling.Height + 20),Constraint.Constant(0)); // Add text label below the image, on the left side of the relative layout

Center UISegmentedControl within a UISearchBar

I am using the built-in search scope with a UISearchDisplayController, and I only have 2 segments.
The problem is that our design needs the buttons to be smaller and centered (it especially looks bad on iPad because the buttons are stretched really wide).
Is there a way to center the UISegmentedControl and make it smaller? I already have the UISegmentedControl pulled out by looping over subViews. And I can set the width of each segment with setWidth:forSegmentAtIndex, but the control is docked to the left. How can I center it?
PS - my app is MonoTouch (Xamarin.iOS), but Obj-C answers are welcome.
Are you adding this via IB or programmatically? In IB, I had to turn off "Autoresize Subviews" and do the resizing of the control dynamically via code. I put the controls I needed to resize into a view that I could bind to then center my control within that view. Here's a sample. I had 2 buttons that I put side-by-side in landscape mode, but it should give you an idea.
// get the current sizes of the things we are moving
CGRect saveRect = self.viewButtons.frame; // the enclosing view
CGRect addLocRect = self.buttonAddLocation.frame; // button 1
CGRect connectRect = self.buttonConnect.frame; // button 2
// This will be set below in one of the if-else branches
CGFloat buttonWidth = 0;
// determine the offset from the left/right based on device and orientation
int offsetLeft = 0;
int offsetRight = 0;
if ([self isIphone]) {
offsetLeft = (UIDeviceOrientationIsPortrait(toInterfaceOrientation)) ? OFFSET_LEFT_PORTRAIT_IPHONE : OFFSET_LEFT_LANDSCAPE_IPHONE;
offsetRight = (UIDeviceOrientationIsPortrait(toInterfaceOrientation)) ? OFFSET_RIGHT_PORTRAIT_IPHONE : OFFSET_RIGHT_LANDSCAPE_IPHONE;
} else {
offsetLeft = (UIDeviceOrientationIsPortrait(toInterfaceOrientation)) ? OFFSET_LEFT_PORTRAIT_IPAD : OFFSET_LEFT_LANDSCAPE_IPAD;
offsetRight = offsetLeft;
}
// change the size & location of the buttons to maximize the area for the location list
// no matter what orientation, the button frame will fill the bottom of the screen
saveRect.size.width = _windowWidth -offsetLeft - offsetRight;
// for Landscape, move the buttons to side-by-side at the bottom of the window
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
// size & move the buttons to fit side-by-side
buttonWidth = (saveRect.size.width)*.4;
// addLocRect.origin.x += offset;
addLocRect.origin.y = saveRect.size.height - addLocRect.size.height ;
connectRect.origin.x = saveRect.size.width - buttonWidth - offsetRight;
connectRect.origin.y = saveRect.size.height - connectRect.size.height;
} else { // Portrait
// move the buttons down to the bottom of the frame, stacked
// size the buttons to be fully across the screen
buttonWidth = saveRect.size.width-2*offsetLeft;
addLocRect.origin.y = 0 ; // at the top of the button view
addLocRect.origin.x = offsetLeft;
connectRect.origin.y = saveRect.size.height - connectRect.size.height;
connectRect.origin.x = offsetLeft;
}
connectRect.size.width = buttonWidth;
addLocRect.size.width = buttonWidth;
self.buttonAddLocation.frame = addLocRect;
self.buttonConnect.frame = connectRect;

WPF printing/xps issue

I have written the following chunk of code that prints my ListBox perfectly when being sent to a physical printer, however when trying to send it to the XPS printer driver or using the XpsDocumentWriter class (I assume they use the same code under the hood) I receive the following exception:
System.ArgumentException was unhandled by user code
Message=Width and Height must be non-negative.
Source=ReachFramework
StackTrace:
at System.Windows.Xps.Serialization.VisualSerializer.WriteTileBrush(String element, TileBrush brush, Rect bounds)
The exception obviously points to an item not having a correct width/height however I have debugged the code when sending it to the different printers (physical and XPS driver) and I haven't been able to find any differences.
Below is how I create the visual to send to the printer:
private ScrollViewer GeneratePrintableView()
{
ScrollViewer scrollView = new ScrollViewer();
Grid grid = new Grid { Background = Brushes.White, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions[0].Height = new GridLength(0, GridUnitType.Auto);
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Auto);
// Add the title and icon to the top
VisualBrush titleClone = new VisualBrush(this.TitleBar);
var titleRectangle = new Rectangle { Fill = titleClone, Width = this.TitleBar.ActualWidth, Height = this.TitleBar.ActualHeight };
grid.Children.Add(titleRectangle);
Grid.SetRow(titleRectangle, 0);
this.myListBox.Width = this.myListBox.ActualWidth;
this.myListBox.Height = this.myListBox.ActualHeight;
VisualBrush clone = new VisualBrush(this.myListBox) { Stretch = Stretch.None, AutoLayoutContent = true };
var rectangle = new Rectangle { Fill = clone, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
Border border = new Border { Background = Brushes.White, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
border.Child = rectangle;
grid.Children.Add(border);
Grid.SetRow(border, 1);
scrollView.Width = this.myListBox.ActualWidth;
scrollView.Height = this.myListBox.ActualHeight;
scrollView.Content = grid;
scrollView.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
return scrollView;
}
Here is the GetPage override in my DocumentPaginator implementation:
public override DocumentPage GetPage(int pageNumber)
{
Page page = new Page();
double z = 0.0;
this.grid = new Grid();
this.grid.RowDefinitions.Add(new RowDefinition());
this.grid.RowDefinitions[0].Height = new GridLength(0, GridUnitType.Auto);
this.grid.Children.Add(this.printViewer);
Grid.SetRow(this.printViewer, 0);
//Adjusting the vertical scroll offset depending on the page number
if (pageNumber + 1 == 1) //if First Page
{
this.printViewer.ScrollToVerticalOffset(0);
this.printViewer.UpdateLayout();
}
else if (pageNumber + 1 == _verticalPageCount) //if Last Page
{
if (this.printViewer.ScrollableHeight == 0) //If printing only single page and the contents fits only on one page
{
this.printViewer.ScrollToVerticalOffset(0);
this.printViewer.UpdateLayout();
}
else if (this.printViewer.ScrollableHeight <= this.printViewer.Height) //If scrollconentheight is less or equal than scrollheight
{
this.printViewer.Height = this.printViewer.ScrollableHeight;
this.printViewer.ScrollToEnd();
this.printViewer.UpdateLayout();
}
else //if the scrollcontentheight is greater than scrollheight then set the scrollviewer height to be the remainder between scrollcontentheight and scrollheight
{
this.printViewer.Height = (this.printViewer.ScrollableHeight % this.printViewer.Height) + 5;
this.printViewer.ScrollToEnd();
this.printViewer.UpdateLayout();
}
}
else //Other Pages
{
z = z + this.printViewer.Height;
this.printViewer.ScrollToVerticalOffset(z);
this.printViewer.UpdateLayout();
}
page.Content = this.grid; //put the grid into the page
page.Arrange(new Rect(this.originalMargin.Left, this.originalMargin.Top, this.ContentSize.Width, this.ContentSize.Height));
page.UpdateLayout();
return new DocumentPage(page);
}
Interestingly if I change the Fill of rectangle to a Brush instead of clone then I do not receive the exception and the outputted file is the correct size.
I have spent over a day trying to debug why this isn't working and I am hoping that someone out there has either seen a similar issue or is able to point out any mistakes I am making.
Thanks for any responses.
I had to give up finding a solution with VisualBrush. If there is a GroupBox in the Visual for the brush I could never get it to produce a XPS file. It always fails with
System.ArgumentException was unhandled by user code Message=Width and Height must be non-negative. Source=ReachFramework StackTrace: at System.Windows.Xps.Serialization.VisualSerializer.WriteTileBrush(String element, TileBrush brush, Rect bounds)
The workaround was to clone the content that should go in the VisualBrush (Is there an easy/built-in way to get an exact copy (clone) of a XAML element?) and use that directly in a Grid instead of an VisualBrush
Have you checked the value of ActualWidth and ActualHeight of myListBox when the VisualBrush is being created? I don't know from where myListBox comes, but if it is not rendered by the time you are generating your xps document you may run into problems. You can try to manually force the control to render and see if it makes any difference.
I was unable to rectify the problem however using this link Paginated printing of WPF visuals I was able to find a suitable solution to allow printing of complicated visuals within my WPF application.
It's 2016 now and it's still not fixed. The problem is using TileBrush or any descendant type (VisualBrush in your case). If you use absolute mapping, it works, it's the relative mapping that causes the problem. Calculate the final size yourself and set Viewport to this size, ViewportUnits to Absolute. Also make sure you don't use Stretch.

Categories