Having the same control width between multiple devices - xamarin

I am developing an app with Xamarin. When I debug, I use an Samsung Phone (SM-A520W, 1920x1080, 424ppi, 5.2 in).
However, the final product will be on a different device (Juniper Allegro 3, 640x480, 4.2in, Android 7.1). The final device is expensive, so this is why not every developer have one.
Is there any way to make the Samsung phone show a grid with the same width has it would be on the final device? Something that looks like the grid is centered and there are empty spaces on each side.
This would help because, when I design a form, the developer who has the final device must always test and readjust my code because we use devices with different resolutions and pixel density (the columns headers are not always readable).
Note: The app is in landscape, if this is relevant.
I have tried this with no success : how to transform a pixel to xamarin.forms units?

Yes, you can use DeviceDisplay to get the width of the screen and adjust it with each resolution:
DisplayInfo screenSize = DeviceDisplay.MainDisplayInfo;
double width = screenSize.Width;
A simple idea is:
You code for your resolution (1920x1080) and then you check the resolution of the phone that is running your project, to get it and return something. In my case, I return a value to divide, but I do it with a image (it's another case, but you can follow the idea, for example, return the width of the Grid for each resolution):
public int GetHeight()
{
int height = (int)DeviceDisplay.MainDisplayInfo.Height;
switch (height)
{
case (640):
return 1;
case (800):
return 2;
case (1024):
return 3;
case (1366):
return 4;
case (1920):
return 5;
case (2048):
return 6;
case (2560):
return 7;
case (3840):
return 8;
case (4096):
return 9;
default:
//1280
return 0;
}
}

Xamarin sizes are supposed to be device-independent. Only need to take care that any "FillAndExpand" or padding/margin differences don't distort the HeightRequest or WidthRequest.
As a rule, WidthRequest="160" will give you 1 inch width in all devices. (Or 64 for 1 centimetre).
To be sure, test in two different devices which are in your access and make sure the sizes stay same across them.
Read more here: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/creating-mobile-apps-xamarin-forms/summaries/chapter05
Also: https://learn.microsoft.com/en-us/xamarin/android/app-fundamentals/resources-in-android/resources-for-varying-screens

You could set the grid' size according to the screensize via Converter. Binding the screensize for the grid first. And then use the converter to set the grid size.
You could use the Xamarin.Essentials to get the screen size.
// Get Metrics
var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
// Width (in pixels)
var width = mainDisplayInfo.Width;
// Height (in pixels)
var height = mainDisplayInfo.Height;
And then use the Converter to set with the specific value.
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value / 3.0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
For the binding Value of Converters, please check the link below: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/converters
For more code, you could check the sample i done before. Resizing frame and controls according to device size .Suggestions?

Related

Add Image dynamically in runtime - Unity 5

In my unity based Android game I wish to add the image dynamically based on the number of questions in each level. The image is shown for reference. Each correct answer will be marked in green and the wrong one in red. I am new to unity and trying hard to find steps to achieve this.
Any help with an example for this requirement will be a great help.
I once wrote a script for dynamically creating buttons based on each level. What I did was creating the first button on the scene and adding the other buttons based on the first one. Below is the shell of my code:
// tutorialButton and levelButtons are public variables which can be set from Inspector
RectTransform rect = tutorialButton.GetComponent<RectTransform> ();
for (int i = 1; i < levelSize; i++) {
// Instantiate the button dynamically
GameObject newButton = GameObject.Instantiate (tutorialButton);
// Set the parent of the new button (In my case, the parent of tutorialButton)
newButton.transform.SetParent (levelButtons.transform);
//Set the scale to be the same as the tutorialButton
newButton.transform.localScale = tutorialButton.transform.localScale;
//Set the position to the right of the tutorialButton
Vector3 position = tutorialButton.transform.localPosition;
position.x += rect.rect.width*i;
newButton.transform.localPosition = position;
}
I am not exactly sure if this is the right approach as it may or may not give unexpected results depending on different screen sizes and your canvas, but hopefully it gives you an idea about dynamically creating objects.
I'm not sure if this helps, but if you have all the images in the scene under a canvas, with this you just need to drag the canvas on the script and use
//level-1 is to keep the array notation
FindObjectOfType<NameOfScript>.ChangeColor(level-1,Color.green);
or you can do also
//level-1 is to keep the array notation
FindObjectOfType<NameOfScript>.RevertColor(level - 1);
This is the script:
//Keep it private but you still see it in inspector
//#Encapsulation :)
[SerializeField]
private Canvas _canvas;
private Image[] _images;
//keep the original colors in case you want to change back
private Color[] _origColors;
void Start () {
_images = GetComponentsInChildren<Image>();
_origColors = new Color[_images.Length];
for (int i = 0; i < _images.Length; i++)
{
_origColors[i] = _images[i].color;
}
}
//Reverts the color of the image back to the original
public void RevertToOriginal(int imageIndex)
{
_images[imageIndex].color = _origColors[imageIndex];
}
//Change to color to the coresponding index, starts from 0
public void ChangeColor(int imageIndex, Color color)
{
_images[imageIndex].color = color;
}
P.S If you want it visible only at the end you can make a method where you enable = (true or false) for the canvas. So you keep it false till the end of the level and you make it true when you want to show, while after every answer you call the ChangeColor depending on the result.
To make it easier you can use:
NameOfScript variableName = FindObjectOfType<NameOfScript>();
and after that you just call
variableName.ChangeColor(level - 1, Color.green);
Also it does not matter where you put the script. I would make some kind of manager(empty GameObject) in the scene and put it there.

Scale UI for multiple resolutions/different devices

I have a quite simple unity GUI that has the following scheme :
Where Brekt and so are buttons.
The GUI works just fine on PC and is on screen space : overlay so it is supposed to be adapted automatically to fit every screen.
But on tablet the whole GUI is smaller and reduced in the center of the screen, with huge margins around the elements (can't join a screenshot now)
What is the way to fix that? Is it something in player settings or in project settings?
Automatically scaling the UI requires using combination of anchor,pivot point of RecTransform and the Canvas Scaler component. It is hard to understand it without images or videos. It is very important that you thoroughly understand how to do this and Unity provided full video tutorial for this.You can watch it here.
Also, when using scrollbar, scrollview and other similar UI controls, the ContentSizeFitter component is also used to make sure they fit in that layout.
There is a problem with MovementRange. We must scale this value too.
I did it so:
public int MovementRange = 100;
public AxisOption axesToUse = AxisOption.Both; // The options for the axes that the still will use
public string horizontalAxisName = "Horizontal"; // The name given to the horizontal axis for the cross platform input
public string verticalAxisName = "Vertical"; // The name given to the vertical axis for the cross platform input
private int _MovementRange = 100;
Vector3 m_StartPos;
bool m_UseX; // Toggle for using the x axis
bool m_UseY; // Toggle for using the Y axis
CrossPlatformInputManager.VirtualAxis m_HorizontalVirtualAxis; // Reference to the joystick in the cross platform input
CrossPlatformInputManager.VirtualAxis m_VerticalVirtualAxis; // Reference to the joystick in the cross platform input
void OnEnable()
{
CreateVirtualAxes();
}
void Start()
{
m_StartPos = transform.position;
Canvas c = GetComponentInParent<Canvas>();
_MovementRange = (int)(MovementRange * c.scaleFactor);
Debug.Log("Range:"+ _MovementRange);
}
void UpdateVirtualAxes(Vector3 value)
{
var delta = m_StartPos - value;
delta.y = -delta.y;
delta /= _MovementRange;
if (m_UseX)
{
m_HorizontalVirtualAxis.Update(-delta.x);
}
if (m_UseY)
{
m_VerticalVirtualAxis.Update(delta.y);
}
}
void CreateVirtualAxes()
{
// set axes to use
m_UseX = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyHorizontal);
m_UseY = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyVertical);
// create new axes based on axes to use
if (m_UseX)
{
m_HorizontalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(horizontalAxisName);
CrossPlatformInputManager.RegisterVirtualAxis(m_HorizontalVirtualAxis);
}
if (m_UseY)
{
m_VerticalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(verticalAxisName);
CrossPlatformInputManager.RegisterVirtualAxis(m_VerticalVirtualAxis);
}
}
public void OnDrag(PointerEventData data)
{
Vector3 newPos = Vector3.zero;
if (m_UseX)
{
int delta = (int)(data.position.x - m_StartPos.x);
delta = Mathf.Clamp(delta, -_MovementRange, _MovementRange);
newPos.x = delta;
}
if (m_UseY)
{
int delta = (int)(data.position.y - m_StartPos.y);
delta = Mathf.Clamp(delta, -_MovementRange, _MovementRange);
newPos.y = delta;
}
transform.position = new Vector3(m_StartPos.x + newPos.x, m_StartPos.y + newPos.y, m_StartPos.z + newPos.z);
UpdateVirtualAxes(transform.position);
}

Custom ListPopupWindow rendered in different widths

I am using Android.Support.V7.Widget.ListPopupWindow as a Drop-Down Menu from a Button within my layout. Here is the code snippet I am using
void MenuIcon_Click (object sender, EventArgs e)
{
popupWindow = new Android.Support.V7.Widget.ListPopupWindow (this);
popupAdapter = new MenuPopUpAdapter (this,selectedIndex,menuList);
popupAdapter.ItemClick+= PopupAdapter_ItemClick;
popupWindow.SetAdapter (popupAdapter);
popupWindow.AnchorView = menuButton;
Display display = WindowManager.DefaultDisplay;
Point size = new Point();
display.GetSize (size);
int width = size.X;
popupWindow.Width =160;
popupWindow.Show ();
}
But while debugging I noted that, even though I have given it a static width, it is rendered differently in different devices. What is causing this issue ?
This is because of the different screen densities in Android devices. You need to mention dimensions in DPs(Density Independent Pixels) to overcome this issue. This documentation from Google will be a nice read
You can get the corresponding pixel value to be mentioned while setting dimensions programatically from this method.
public int dpToPx(int dp) {
DisplayMetrics displayMetrics = Resources.DisplayMetrics;
int px = (int)Math.Round(dp * (displayMetrics.Density));
return px;
}
You may modify the code as above to fix the issue
popupWindow.Width =dpToPx(160);

Making an NSTableView behave like a WPF StackPanel

I'm trying to implement a vertical StackPanel equivalent in my MonoMac project and am having trouble figuring it out. I am not using interface builder but creating the controls programmatically (this is a restriction). I tried using a CollectionView but all items in that control are sizing to the same height.
Looking around the internet, it seems NSTableView is the way to go, but I'm not sure how to do it, especially with C# while using a MonoMac target. CollectionView was somewhat more straightforward with the CollectionViewItem.ItemPrototype allowing me to create the views I want to render. But with an NSTableView, it seems like I can only specify a data source that returns the NSObjects I want to display. How do I grab this data and then bind them to the view I want to stick in there?
I would prefer C# code but I'm at a stage where I'll accept any and all help!
I was finally able to get it working. Here is some code for anyone who wants to try it out. Basically, we need to write NSTableViewDelegates for the required functions. This implementation also doesn't cache the controls or anything. The Cocoa API documentation mentioned using an identifier to reuse the control, or something, but the identifier field is get-only in MonoMac.
I also ended up implementing my NSTableViewDelegate functions in my data-source itself which I am sure is not kosher at all, but I'm not sure what the best practice is.
Here's the data source class:
class MyTableViewDataSource : NSTableViewDataSource
{
private NSObject[] _data;
// I'm coming from an NSCollectionView, so my data is already in this format
public MyTableViewDataSource(NSObject[] data)
{
_data = data;
}
public override int GetRowCount(NSTableView tableView)
{
return _data.Length;
}
#region NSTableViewDelegate Methods
public NSView GetViewForItem(NSTableView tableView, NSTableColumn tableColumn, int row)
{
// MyViewClass extends NSView
MyViewClass result = tableView.MakeView("MyView", this) as MyViewClass;
if (result == null)
{
result = new MyViewClass(_data[row]);
result.Frame = new RectangleF(0, 0, tableView.Frame.Width, 100); // height doesn't matter since that is managed by GetRowHeight
result.NeedsDisplay = true;
// result.Identifier = "MyView"; // this line doesn't work because Identifier only has a getter
}
return result;
}
public float GetRowHeight(NSTableView tableView, int row)
{
float height = FigureOutHeightFromData(_data[row]); // run whatever algorithm you need to get the row's height
return height;
}
#endregion
}
And here's the snippet that programmatically creates the table:
var tableView = new NSTableView();
var dataSource = new MyTableViewDataSource();
tableView.DataSource = dataSource;
tableView.HeaderView = null; // get rid of header row
tableView.GetViewForItem = dataSource.GetViewForItem;
tableView.GetRowHeight = dataSource.GetRowHeight;
AddSubView(tableView);
So, it is not a perfect StackPanel because one needs to manually calculate row heights, but it's better than nothing.

ASp.Net Mvc 1.0 Dynamic Images Returned from Controller taking 154 seconds+ to display in IE8, firefox and all other browsers fast and easy

I have a curious problem with IE, IIS 6.0 dynamic PNG files and I am baffled as to how to fix..
Snippet from Helper (this returns the URL to the view for requesting the images from my Controller.
string url = LinkBuilder.BuildUrlFromExpression(helper.ViewContext.RequestContext, helper.RouteCollection, c => c.FixHeight(ir.Filename, ir.AltText, "FFFFFF"));
url = url.Replace("&", "&");
sb.Append(string.Format("<removed id=\"TheImage\" src=\"{0}\" alt=\"\" />", url)+Environment.NewLine);
This produces a piece of html as follows:-
img id="TheImage" src="/ImgText/FixHeight?sFile=Images%2FUser%2FJulianGuppy%2FMediums%2Fconservatory.jpg&backgroundColour=FFFFFF" alt="" /
brackets missing because i cant post an image... even though I dont want to post an image I jsut want to post the markup... sigh
Snippet from Controller ImgTextController
/// <summary>
/// This function fixes the height of the image
/// </summary>
/// <param name="sFile"></param>
/// <param name="alternateText"></param>
/// <param name="backgroundColour"></param>
/// <returns></returns>
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult FixHeight(string sFile, string alternateText, string backgroundColour)
{
#region File
if (string.IsNullOrEmpty(sFile))
{
return new ImgTextResult();
}
// MVC specific change to prepend the new directory
if (sFile.IndexOf("Content") == -1)
{
sFile = "~/Content/" + sFile;
}
// open the file
System.Drawing.Image img;
try
{
img = System.Drawing.Image.FromFile(Server.MapPath(sFile));
}
catch
{
img = null;
}
// did we fail?
if (img == null)
{
return new ImgTextResult();
}
#endregion File
#region Width
// Sort out the width from the image passed to me
Int32 nWidth = img.Width;
#endregion Width
#region Height
Int32 nHeight = img.Height;
#endregion Height
// What is the ideal height given a width of 2100 this should be 1400.
var nIdealHeight = (int)(nWidth / 1.40920096852);
// So is the actual height of the image already greater than the ideal height?
Int32 nSplit;
if (nIdealHeight < nHeight)
{
// Yes, do nothing, well i need to return the iamge...
nSplit = 0;
}
else
{
// rob wants to not show the white at the top or bottom, so if we were to crop the image how would be do it
// 1. Calculate what the width should be If we dont adjust the heigt
var newIdealWidth = (int)(nHeight * 1.40920096852);
// 2. This newIdealWidth should be smaller than the existing width... so work out the split on that
Int32 newSplit = (nWidth - newIdealWidth) / 2;
// 3. Now recrop the image using 0-nHeight as the height (i.e. full height)
// but crop the sides so that its the correct aspect ration
var newRect = new Rectangle(newSplit, 0, newIdealWidth, nHeight);
img = CropImage(img, newRect);
nHeight = img.Height;
nWidth = img.Width;
nSplit = 0;
}
// No, so I want to place this image on a larger canvas and we do this by Creating a new image to be the size that we want
System.Drawing.Image canvas = new Bitmap(nWidth, nIdealHeight, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(canvas);
#region Color
// Whilst we can set the background colour we shall default to white
if (string.IsNullOrEmpty(backgroundColour))
{
backgroundColour = "FFFFFF";
}
Color bc = ColorTranslator.FromHtml("#" + backgroundColour);
#endregion Color
// Filling the background (which gives us our broder)
Brush backgroundBrush = new SolidBrush(bc);
g.FillRectangle(backgroundBrush, -1, -1, nWidth + 1, nIdealHeight + 1);
// draw the image at the position
var rect = new Rectangle(0, nSplit, nWidth, nHeight);
g.DrawImage(img, rect);
return new ImgTextResult { Image = canvas, ImageFormat = ImageFormat.Png };
}
My ImgTextResult is a class that returns an Action result for me but embedding the image from a memory stream into the response.outputstream.
snippet from my ImageResults
/// <summary>
/// Execute the result
/// </summary>
/// <param name="context"></param>
public override void ExecuteResult(ControllerContext context)
{
// output
context.HttpContext.Response.Clear();
context.HttpContext.Response.ContentType = "image/png";
try
{
var memStream = new MemoryStream();
Image.Save(memStream, ImageFormat.Png);
context.HttpContext.Response.BinaryWrite(memStream.ToArray());
context.HttpContext.Response.Flush();
context.HttpContext.Response.Close();
memStream.Dispose();
Image.Dispose();
}
catch (Exception ex)
{
string a = ex.Message;
}
}
Now all of this works locally and lovely, and indeed all of this works on my production server
BUT Only for Firefox, Safari, Chrome (and other browsers) IE has a fit and decides that it either wont display the image or it does display the image after approx 154seconds of waiting.....
I have made sure my HTML is XHTML compliant, I have made sure I am getting no Routing errors or crashes in my event log on the server....
Now obviously I have been a muppet and have done something wrong... but what I cant fathom is why in development all works fine, and in production all non IE browsers also work fine, but IE 8 using IIS 6.0 production server is having some kind of problem in returning this PNG and I dont have an error to trace... so what I am looking for is guidance as to how I can debug this problem.
Looks like it might be time to install Fiddler or HttpWatch and find out what is going on.
Well, what I wanted to do was to convert my images (jpg, bmp, etc... and return them as png) - however. Although all other browsers would accept the encoding and display them fine, IE decided to hide the png image (after partially displaying it) because it thought it wasn't a PNG image. (Even though it was) So I gave up with the conversion methods and just returned the image in its original format. ---
if anybody comes up with a way of reliably converting any image format into png so that I can return it dynamically from a memory stream, then please do let me know....
So I am closing this for now as IE has won this battle -
I wish IE would just sod off and die. I hate IE so much. Seriously it just sucks the sweat of a dead mans balls.
Never call Response.End()
At least, that's what the maintainer for the HttpResponse documentation says.
Response.End() ends the http connection, often orphaning data in the server's buffer or somewhere in the stream. It's better to let IIS end the connection (or reuse it) instead, since many clients don't have a perfect network stack.
IE has a terrible network stack (as anyone who has used IETab for FF can witness, IE is almost fast when placed on top of the FF network pipe). The other browsers handle the quickly closed connection much better, and so probably aren't responding badly to the closed connection.
On a different topic, it looks like you are re-inventing the wheel. It's not an easy wheel to build properly, either.
Based on the code I see, it looks like you are leaking the GDI bitmap 'img', and additional leaks would occur if anything unexpected happened during the request. Note that .NET does not garbage collection GDI handles properly, and it's one of the biggest causes of server crashes - make sure you get that fixed, my friend :)

Resources