Getting ActualWidth in Windows Phone8.1 - xaml

I'd like to get the actual width of an image in my WP8.1 app. The width cannot be determined (i.e. is zero) until the page has been rendered, and other solutions suggest handling this in the page loaded event as in the basic example below. But even here, img.ActualWidth is zero.
How can I retrieve img.ActualWidth once as soon as the page is rendered?
public sealed partial class MainPage : Page {
public MainPage() {
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
this.Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e) {
Debug.WriteLine(img.ActualWidth);
}
}
and
<Page
x:Class="Page_Loaded.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Page_Loaded"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Image x:Name="img" Source="/image.jpg" />
</Grid>
</Page>

Use SizeChanged event handler. It occurs when either the ActualHeight or the ActualWidth property changes value on a FrameworkElement.
ActualWidth is a calculated property. The calculations are a result of a layout pass, where the object is sized in layout according to the logic of its successive layout parents.
The default of ActualWidth is 0. The default might be encountered if the object has not been loaded and hasn't yet been involved in a layout pass that renders the UI. (This is happening in your case)
ActualWidth can have multiple or incremental reported changes to the value because of operations by the layout system. If you get the value while layout is still iterating, the layout system might still be calculating the required measure of space for child objects, constraints by the parent object, and so on. Because the value is based on an actual rendering pass, it may lag slightly behind the set value of properties like Width, which can be the basis of the input change.
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.
SizeChanged fires whenever the size (either ActualHeight or ActualWidth) has changed on the object, which is after the Measure and Arrange passes are complete. One reason to handle the SizeChanged event is to see whether the ratio of an element's ActualHeight versus ActualWidth have changed, because of a new layout.

Related

Mouse events are not triggered in Avalonia user control

I'm trying to add Pan and Zoom to custom user control in Avalonia 0.10.10 using mouse wheel and mouse move events.
Standard avalonia template
dotnet new avalonia.mvvm -o Demo
User Control XAML
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="CrossCanvas.Views.Avalonia.CanvasView">
</UserControl>
User Control
public partial class CanvasView : UserControl // Canvas
{
public CanvasView()
{
AvaloniaXamlLoader.Load(this);
PointerMoved += OnMouseMove;
PointerWheelChanged += OnWheel;
}
protected void OnWheel(object sender, PointerWheelEventArgs e) {}
protected void OnMouseMove(object sender, PointerEventArgs e) {}
}
Code that was used as an example
https://github.com/AvaloniaUI/Avalonia/issues/2492#issuecomment-489898224
The issue
No matter where I add new event handlers, in the constructor or in EndInit handler, these events are not being triggered. Maybe Avalonia 0.10.10 had some breaking changes or I do something wrong?
UserControl is invisible for hit test (pointer input) for obvious reasons to avoid it being blocking input from any other control behind it.
But you can easily make it visible for hit test by making it "visible" - set Background to non-null value. For example, "Transparent".

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>

What element to use if I only need an placement target element

I want to show a flyout at a specific place. I want to specify a placement target element in XAML, but I want to make sure I am using the "lightest" element possible, given that I don't want that element to ever be visible or interacted with.
Is there a "recommended" or "correct" element to use? If not, what would be the "lightest" element to use? Or am I overthinking this and should just use a button?
I want to show a flyout at a specific place.
The place of FrameworkElements are based on the panel that you are using to hold them. If you want to show your flyout based on a FrameworkElement being placed in a specific place, you can use Canvas to position your FrameworkElement.
I want to specify a placement target element in XAML, but I want to make sure I am using the "lightest" element possible.
Flyout.ShowAt takes FrameworkElement as it's placement target. So I think the "lightest" element would be an empty custom FrameworkElement like below:
public class MyElement:FrameworkElement
{
}
And you can put it into XAML and use Canvas to position it:
<Page
x:Class="PopupSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PopupSample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Canvas>
<Button Name="btnClick" Canvas.Left="50" Canvas.Top="500" Click="btnClick_Click">Click Me</Button>
<local:MyElement x:Name="myEle" Canvas.Left="100" Canvas.Top="100"></local:MyElement>
</Canvas>
Code-Behind:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void btnClick_Click(object sender, RoutedEventArgs e)
{
Flyout flyout = new Flyout();
TextBlock tbContent = new TextBlock
{
Text= "this is a flyout content"
};
flyout.Content = tbContent;
flyout.ShowAt(myEle);
}
}
Grid is a pretty light element, it's just a simple Panel-derived class without any child elements. Button is a Control, meaning it has a template which will create many child elements that make up its visual appearance.
Are you saying you want to use a dummy element just for the purpose of specifying the position of the flyout which you will show programmatically? If you want to avoid the element altogether, then maybe a Popup would be a better choice.

What is the difference between null and transparent brush in the Background or Fill

For example we have a Border. What the difference beetween these XAMLs?
1) Background="Transparent"
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Border
BorderBrush="White"
BorderThickness="2"
Width="400"
Height="400"
Background="Transparent"
PointerPressed="Border_PointerPressed"
PointerReleased="Border_PointerReleased" />
</Grid>
2) Background="{x:Null}"
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Border
BorderBrush="White"
BorderThickness="2"
Width="400"
Height="400"
Background="{x:Null}"
PointerPressed="Border_PointerPressed"
PointerReleased="Border_PointerReleased" />
</Grid>
Both of these borders looks identical. But what the difference?
The difference is if we set null background the Border will not support hit-testing, that's why routed events like PonterPressed will not be raised.
Conversely though, if we set Transparent background events will be raised.
To illustrate this let's write code-behind.
using Windows.UI.Xaml.Media;
namespace App1 {
public sealed partial class MainPage : Page {
public MainPage() {
this.InitializeComponent();
}
void Border_PointerPressed(object sender, PointerRoutedEventArgs e) {
Border border = sender as Border;
if (border != null)
border.Background = new SolidColorBrush(Colors.Red);
}
void Border_PointerReleased(object sender, PointerRoutedEventArgs e) {
Border border = sender as Border;
if (border != null)
border.Background = new SolidColorBrush(Colors.Transparent);
}
}
}
1) Let's use the first XAML, compile our app and run it. Try to tap inside the square. The square becomes red because the events are rised and the handlers calls.
2) Now let's use the second XAML, compile the app, run it, tap inside the square. Nothing happens because the events are not rised. The handlers are not calls.
For completeness, I found this link http://msdn.microsoft.com/en-us/library/hh758286.aspx#hit_testing explaining this rather well - see especially the second bullet point:
Hit testing and input events
Determining whether and where in UI an element is visible to mouse,
touch, and stylus input is called hit testing. For touch actions and
also for interaction-specific or manipulation events that are
consequences of a touch action, an element must be hit-test visible in
order to be the event source and fire the event that is associated
with the action. Otherwise, the action passes through the element to
any underlying elements or parent elements in the visual tree that
could interact with that input. There are several factors that affect
hit testing, but you can determine whether a given element can fire
input events by checking its IsHitTestVisible property. This property
returns true only if the element meets these criteria:
The element's Visibility property value is Visible.
The element's Background or Fill property value is not null. A null Brush value results in transparency and hit test invisibility. (To
make an element transparent but also hit testable, use a Transparent
brush instead of null.) Note Background and Fill aren't defined by
UIElement, and are instead defined by different derived classes such
as Control and Shape. But the implications of brushes you use for
foreground and background properties are the same for hit testing and
input events, no matter which subclass implements the properties.
If the element is a control, its IsEnabled property value must be true.
The element must have actual dimensions in layout. An element where either ActualHeight and ActualWidth are 0 won't fire input events.

Let ListView scroll to selected item

I have a WinRT/C#/XAML app with a view that has a vertical ListView of items. Depending on the amount of items the ListView shows a vertical scrollbar. Here's the XAML definition:
<UserControl.Resources>
<CollectionViewSource
x:Name="myViewSource"
Source="{Binding myViewModel.Items}" />
</UserControl.Resources>
...
<ListView
x:Name="myListView"
ItemsSource="{Binding Source={StaticResource myViewSource}}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
</ListView>
Now everytime I navigate to this view, the selected item of the ListView is chosen by setting the databound SelectedItem property in the view model from code behind (OnNavigatedTo). My problem: the ListView doesn't scroll automatically to this selected item. The scrollbar remains at the top of the ListView and the user has to scroll manually to see the selected item.
I tried to execute myListView.ScrollIntoView(MyViewModel.SelectedItem); after setting the SelectedItem in the code behind (in OnNavigatedTo), but it doesn't work. The scrollbar remains at the top.
I'm aware of this thread on SO: Scroll WinRT ListView to particular group .
This seems to be a similar problem. But when I walk the visual tree of the ListView manually or with the WinRT XAML Toolkit, it doesn't find a ScrollViewer (returns null instead).
Thanks to Filip I noticed that calling ScrollIntoView() in OnNavigatedTo() was too early, because the ListView control is not loaded yet in this place.
The first solution idea was to bind the Loaded event of the ListView:
myListView.Loaded += (s, e) =>
myListView.ScrollIntoView(MyViewModel.SelectedItem);
Unfortunately that causes a nasty visual effect, where current ListView items overlap with the selected item for parts of a second, before everything is rearranged well.
The final solution I found is to call ScrollIntoView() asynchronously via the Dispatcher of the view:
myListView.Loaded += (s, e) => Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => myListView.ScrollIntoView(MyViewModel.SelectedItem));
With this solution the layouting works fine.
I had a similar need and resolved it in a slightly different manner. I subscribed to the SelectionChangedEvent from the ListView and performed the scrolling within the handler.
XAML:
<ListView x:Name="myListView" SelectionChanged="myListView_SelectionChanged" ...>
</ListView>
Code:
private void myListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
myListView.ScrollIntoView(myListView.SelectedItem);
}