XAML: accessing actual width and height - xaml

I have a viewbox with an image inside:
<Viewbox MaxHeight="100" MaxWidth="100" x:Name="Scenario4ImageContainer2" Stretch="Uniform" Grid.Column="1" Grid.Row="1">
<Image x:Name="Scenario4Image" PointerPressed="Scenario4Image_PointerPressed" HorizontalAlignment="Stretch" />
</Viewbox>
I want to be able to grab the actual width/height values, but when I try this in the backend C#:
int w = (int)Scenario4ImageContainer.Width
I get an error saying the parameter is incorrect.
This goes away if I hardcode the width, but I want it to resize dynamically.
I also tried grabbing Scenario4ImageContainer.ActualWidth but this parameter was "incorrect" as well.

A while back I was trying to measure width of a string. You can try a similar mechanism to get dimensions.
http://invokeit.wordpress.com/2012/09/19/how-to-measure-rendered-string-dimensions-in-win8dev/
this.tb.FontSize = 20;
this.tb.Measire(new Size(400, 300)); // assuming that 400x300 is max size of textblock you want
double currentWidth = this.tb.DesiredSize.Width;
double currentHeight = this.tb.DesiredSize.Height;

Seems like I've found the event you need to handle: you should handle ImageOpened event. It is because image is retrieved asynchronously and if try to handle any other event there is a good chance to not have image loaded at that time so actual size is zero

On every draw I do a quick check to see if the container size has changed (with an initialization of 0 at the beginning to make sure it catches the first time).
if (containerWidth != Output.ActualWidth - 300)
{
Scenario4ImageContainer.Width = Output.ActualWidth - 300;
Scenario4ImageContainer.Height = Output.ActualHeight - 20;
containerWidth = Output.ActualWidth - 300;
}
It works for the most part, but when the class gets navigated out and navigated back it has a problem for some reason. Probably unrelated.

Related

Xamarin Forms Image with 100% of parent container width and auto Height to maintain aspect ratio

Is it possible to have an Image take 100% width of its parent container while actually fitting the whole image within this container without clipping, and while having height automatically adjusted to preserve aspect ratio?
I have read similar questions both on SO and Xamarin Forums but apparently this cannot be done without implementing custom renderers or manually calculating correct sizes in code. But to calculate this in code you would need either image dimensions or aspect ratio. For applications where neither of these are known before head, this is a problem.
In terms of CSS, the solution I am looking for is similar to having
width:100%; height:auto;
Implementing a custom renderer for such a trivial task is an overkill and a huge embarrassment for Xamarin in my opinion; unless I am understanding something wrong.
I have searched for an answer to this problem for a long time. As others have noted, I did not find a Xaml-only solution. I choose a different route and present my solution for those who might find it instructive and as the seed for changes to FFImageLoading to solve this correctly.
I created a subclass of CachedImage and overrided OnMeasure. In order for CachedImage.OnMeasure to work in the aspect modes (not fill mode), it has to return a SizeRequest which matches the ratio of the underlying image. So the solution I chose was simply to use the provided widthConstraint and calculate the height as widthConstraint / ratio. This description only addresses one of the many cases: where the widthConstraint is not infinity and the desire is to answer the specific question posed here (width of 100% and auto height).
A subclass which implements this case:
public class CachedImage2 : CachedImage
{
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
var sr = base.OnMeasure(widthConstraint, heightConstraint);
if (sr.Request.IsZero)
return sr;
var ratioWH = sr.Request.Width / sr.Request.Height;
var sr2 = new SizeRequest(new Size(widthConstraint, widthConstraint / ratioWH));
return sr2;
}
}
I was unable to find a pure XAML solution to this problem and therefore decided to use ffimageloading's Success event to find out the original width and height of loaded image and get aspect ratio from these values, store it, and use it in SizeAllocated event to maintain aspect ratio of the image while making sure its width is 100% of the parent container.
Sample code:
private void testImage_OnSuccess(object sender, CachedImageEvents.SuccessEventArgs e)
{
var image = sender as CachedImage;
double width = e.ImageInformation.OriginalWidth;
double height = e.ImageInformation.OriginalHeight;
ratio = width / height; // store it in a variable
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
if (this.Width > 0 && ratio != 0) // width of the parent container
testImage.HeightRequest = this.Width / ratio;
}
Presuming when you say:
and while having height automatically adjusted..
You mean the height of the container.
Yes this is completely possible in Xamarin.Forms.
Let's imagine I have a Grid as my parent container. Here is how I would do it.
<!-- remove all the padding & margin & spacing on Grid -->
<Grid RowSpacing="0"
ColumnSpacing="0"
Margin="0"
Padding="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <!-- the containers height will now adjust -->
<RowDefinition Height="56"/> <!-- This one is for the other content in your view etc -->
</Grid.RowDefinitions>
<!-- Put your image inside your parent container and apply properties -->
<Image Source="some_source.png"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"/>
</Grid>
The Horizontal and vertical options are as if you are setting width:100% and height: 100% in CSS.
Put you Image in a frame. And then set the height and width of image accprding to the frame.
<Frame>
<Image></Image>
</Frame>
You could change the width and height according to your frame via binding Value Converters.
Binding Value Converters: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/converters
Set the name to your Frame first.
<Frame
…………
x:Name="frame"/>
Create the MyConverter. You could change the percentage of value in Convert method.
MyConverter.cs
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Set the StaticResource.
<ContentPage.Resources>
<ResourceDictionary>
<local:MyConverter x:Key="MyConverter" />
</ResourceDictionary>
Binding to your image.
<Image WidthRequest="{Binding Source={x:Reference frame},Path=Width,Converter={StaticResource MyConverter}}"
HeightRequest="{Binding Source={x:Reference frame},Path=Height,Converter={StaticResource MyConverter}}"></Image>

XAML : Binding FontSize to the Height of Control

Within a Xamarin.Form page, I am trying to bind the FontSize of a label to the height of that same label.
I seen examples in WPF that do this:
FontSize="{Binding ElementName=CurrentPresenter, Path=Height}"
But I can not seem to get anything to work like that in Forms, i.e.:
<Label
Text="X"
FontSize="{Binding ElementName=CurrentPresenter, Path=RequestedHeight}"
HorizontalTextAlignment="Center"
AbsoluteLayout.LayoutBounds="0.1, 0.5, 0.33, 0.66"
AbsoluteLayout.LayoutFlags="All" />
(I will use a data converter to adjust the font size so based upon screen DPI, I can auto-adjust, but for now, I'm just trying to bind it to a control's height to get something other than default system font size)
This way appears to work fine, not sure about the runtime layout performance though...
FYI: This does not work within the XAML Designer, only runtime
<Label
Text="X"
x:Name="foo"
BindingContext="{x:Reference Name=foo}"
FontSize="{Binding Path=Height}"
HorizontalTextAlignment="Center"
AbsoluteLayout.LayoutBounds="0.1, 0.5, 0.33, 0.66"
AbsoluteLayout.LayoutFlags="All" />
Not exactly related to OP question but searching "Xaml height based on another control" has hardly any results in Google so posting this in case it helps someone else.
To change the size of smaller buttons to match the size of a larger button like in the following picture
There are several ways to do this without using code behind but if the layout you are trying to use is being difficult here following is another option. To change the size of the smaller buttons without changing the binding of those controls you can give the buttons names in XAML and then do something like this in the code behind:
public HomeOpenPage ()
{
InitializeComponent ();
RefreshButton.PropertyChanging += RefreshButton_PropertyChanging;
}
private void RefreshButton_PropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (RefreshButton.Width != -1)
{
var width = RefreshButton.Width;
CSVButton.WidthRequest = width;
EndButton.WidthRequest = width;
}
}
protected override void OnDisappearing()
{
RefreshButton.PropertyChanging -= RefreshButton_PropertyChanging;
base.OnDisappearing();
}

Image translation is clipped (how to prevent)

How can I force a UI object to NOT be clipped when it's partly outside the screen. I have an image that doesn't fit inside the screen completely. When I drag it (TranslateY) it moves like it's supposed to but the problem is that the part that was outside the screen doesn't appear so the image is abruptly cut. The only part of the image that is visibly moving is the part that originally fit to the screen.
Ps. Please do not recommend scollviewer as this is about a gesture to do a specific thing on the UI and ScrollViewer is not ok for that.
This is basically the XAML (The image is twice the height of the display)
<Grid x:Name="GestureScreen" ManipulationMode="TranslateY" RenderTransformOrigin="0.5,0.5">
<Image x:Name="GestureImage" CacheMode="BitMapCache" Source="Assets/bg/draggable.png" />
</Grid>
This is the C# (not really relevant, but still)
void GestureScreen_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
move.Y += e.Delta.Translation.Y;
}
Specify a row and column for the image which directly specifies Auto for both. That way the image will size to its actual size and not the current size of the * defaults of the screen size. Hence you will have the whole image as dragged.

How to Assign a XAML Stype from Common/StandardStyles.xaml in code behind

I have search and tried a number of things to do this. I have a style that is successfully bound to multiple XAML frames by doing XAML code like this, the name of the style is ViewPersonTextboxDataStyle:
<TextBox Grid.Row="5" Grid.Column="1" Name="textboxName" Text="{Binding textboxName}" Margin="5,5,5,5" Style="{StaticResource ViewPersonTextboxDataStyle}"/>
So, when I get to another frame where I want to create the Grid rows and definitions in a code behind, I tried this, which I thought was correct:
var resourceDictionary = new ResourceDictionary()
{
Source = new Uri("ms-appx:///Common/StandardStyles.xaml", UriKind.Absolute)
};
var style = resourceDictionary["ViewPersonTextboxDataStyle"] as Style;
textBlock.Style = resourceDictionary["ViewPersonTextboxDataStyle"] as Style;
So at this point, I can see the style was found in the resource dictionary as style is populated correctly. But assigning into textBlock.Style causes a Catastrophic Exception. So, either I am missing a step or this is incorrect.
Not alot of net information on this.
Ok, thank you Raghavendra, this did point me in the right direction to tell me that things I was trying weren't off base.
What I ended up with is:
style = Application.Current.Resources["ViewPersonTextDataStyle"] as Style;
textBlock.Style = style;
Raghavendra is right, you don't need to use resource manager, and you also don't need to define it in the local XAML. I used the above line to do it by assigning current in every one of my frames anyway.
So with that, my exception was one for an IDIOT (namely me). I should have been using my TEXTBLOCK style not my TEXTBOX style. Assigning a textbox style to the textblock was causing the exception.
Try this:
textBlock.style = this.Resources["ViewPersonTextboxDataStyle"] as Style;
You need not use ResourceDictionary
Edit:
this.Resources refers to Page.Resources (that is in case the ViewPersonTextboxDataStyle is defined in Page.Resources we use this)
Try this one:
textBlock.style = App.Current.Resources["ViewPersonTextboxDataStyle"] as Style;

Zooming in and out in Grid, Images on WinRT (Metro style app)

I'm trying to implement something in my app, I need to show an image, and let the user pinch in and out of the images.
I think its possible using the ScrollViewer, but I couldnt get it to work, help?
I hate to oversimplify this, but the WinRT XAML ScrollViewer has gesture manipulation built in.
You can see what I mean here. This might not be what you want. But it's sure a simple approach and might fit a certain % of scenarios. Maybe even yours.
Controls that incorporate a ScrollViewer in compositing often set a value for ZoomMode in the default template and starting visual states, and it is this templated value that you will typically start with. Controls with a ScrollViewer as part of their composition typically use template binding such that setting the attached property at the level of the control will change the scroll behavior of the ScrollViewer part within the control. Otherwise, it may be necessary to replace the template in order to change the scroll behavior of a ScrollViewer part.
Check out Morten Nielsen's article on Building A Multi-Touch Photo Viewer Control. It's for Silverlight/Windows Phone, but if you just enable manipulations on the image and change a few types in manipulation events - it should work great.
A simple solution that might be enough for you is to just put the image in a ScrollViewer, although to see it working - you need a touch screen or run it in a simulator (use the pinch tool, then drag and scroll on the image to zoom in/out).
You can also zoom it with code:
<Grid
Background="{StaticResource ApplicationPageBackgroundBrush}">
<ScrollViewer
x:Name="myScrollViewer">
<Image
Source="/Assets/SplashScreen.png" />
</ScrollViewer>
</Grid>
.
public BlankPage()
{
this.InitializeComponent();
myScrollViewer.ZoomMode = ZoomMode.Enabled; // default
Test();
}
private async void Test()
{
while (true)
{
for (double x = 0; x < 2 * Math.PI; x += Math.PI / 30)
{
await Task.Delay(1000 / 30);
float factor = (float)(1.0 + Math.Sin(x) / 10);
myScrollViewer.ZoomToFactor(factor);
}
}
}