How to centre an item over a point in a Canvas? - xaml

I'm using a ListBox with its DataTemplate containing a Canvas. I then bind the Left/Top of the Grid containing that Canvas to move it to a certain point.
I want to then have the child Grid centred at the X,Y coordinates I've specified, where the size of the child Grid is variable based on its content. I was planning on achieving this by using a TranslateTransform to move the Grid by half of its width.
I can't see how I can set that TranslateTransform however as ElementName binding doesn't work within a DataTemplate. Any ideas how I can achieve this?
<ItemsControl ItemsSource="{TemplateBinding SomeCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Grid x:Name="Container"
Canvas.Left="{Binding X}"
Canvas.Top="{Binding Y}"
Background="#88000000">
<Grid.RenderTransform>
<TranslateTransform X="{Binding ActualWidth, ElementName=Container, Converter={StaticResource NegativeHalfConverter}}"
Y="{Binding ActualHeight, ElementName=Container, Converter={StaticResource NegativeHalfConverter}}" />
</Grid.RenderTransform>
<TextBlock Text="{Binding SomeValue}" FontSize="36" Foreground="White" />
</Grid>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
`

I think ElementName binding should work in the name scope of a DataTemplate, but I have seen people complaining about bindings not updating correctly when binding to ActualWidth/Height properties. Perhaps instead of doing the complicated setup you have you could just implement an attached behavior that takes a Point parameter and updates the Canvas.Left/Top properties whenever the parameter or size of the associated object (your grid) changes.

Looks like the fault wasn't with the binding itself, but with a feature that means the ActualWidth/ActualHeight properties aren't bindable. Thanks Filip.
To fix this I created a derived Grid with a couple of new dependency properties that I update in the SizeChanged events to have the ActualWidth/Height. I then use my DataTemplate as above, binding to these new DPs to translate and centre my Grid on a point. Seems to work a treat.

To move an object by half of its size you can use 2 rotations or scales: first is over 0.25,0.25 relative point, second is over the enter point 0.5,0.5. If you use rotations then angels are 180 degrees and -180 degrees. If you use scales then scale factors are -1,-1 and -1,-1.
Do not forget about RenderTransformOrigin property. And to apply two transforms you can apply them to two nested elements.

Related

How to change the tab index in avalonia ui

In Avalonia Ui,
I have multiple layouts in my ui and I want a very specific tab order,
something like
<TextBox Tabindex="2">
<StackPanel>
<TextBox Tabindex="1">
</StackPanel>
<TextBox Tabindex="0">
that would result in using the tab-key cycling from bottom to top.
Is this possible? I found nothing.
I don't think so. KeyboardNavigation.TabIndex is not implmented yet: https://github.com/AvaloniaUI/Avalonia/issues/3025.
You have some limited Control using the functions provided in KeyboardNavigation and by using another control, for example a DockPanel or a Relative Panel.
Here is a very basic example for how you can use a DockPanel to do what you wish to:
<DockPanel LastChildFill="False"
VerticalAlignment="Top"
Name="TabOrderConatiner">
<TextBox Name="First" Tag="2" DockPanel.Dock="Top"></TextBox>
<TextBox Name="Third" Tag="0" DockPanel.Dock="Bottom"></TextBox>
<TextBox Name="Second" Tag="1" DockPanel.Dock="Top"></TextBox>
</DockPanel>
However it is like I said above limited compared to what TabIndex can provide.
Apart from that you should also be able to set the first TabItem when the container becomes active like that from your code-behind:
var tabOrderContainer = this.FindControl<DockPanel>("TabOrderConatiner");
var initialElement = this.FindControl<TextBox>("Third");
KeyboardNavigation.SetTabOnceActiveElement(tabOrderContainer, initialElement);
but I did not manage to get this to work...

Using x:Bind inside the GridView's ItemTemplate layout User Control in UWP

In the Universal Windows Platform API, how do I use x:Bind inside of a User Control (intended to be the layout for a GridView's ItemTemplate) to bind to instance properties of a GridView's ItemSource?
Background
I'm trying to re-create the layout found in Windows 10 stock apps like Sports, News, Money, etc.
I'm using a two GridViews for the main area of the app; one for "featured articles" (2 large photos w/ headlines) and one for all the other articles (smaller photos w/ headlines).
I'm able to bind to a data source that I supply in the code behind (a List where NewsItem is a POCO with a Image and Headline property) Here's the pertinent parts of the MainPage.xaml:
<Page ...
xmlns:data="using:NewsApp.Models" />
....
<GridView Name="FeaturedItems" Grid.Row="0">
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:NewsItem">
<Grid Name="mainPanel" HorizontalAlignment="Stretch" Width="500" >
<Image Source="{x:Bind Image}" HorizontalAlignment="Stretch" />
<TextBlock Text="{x:Bind Headline}" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
....
The Image and Headline bind just fine (even though they've not been styled correctly). However, instead I think I need to bind to a User Control to get the styling options I want, control over resizing esp. when using Visual State Triggers and to simplify the XAML in general (at least, this was the technique suggested to me.)
So, I added a new User Control to the project (FeaturedItemControl.xaml), and copied in the DataTemplate's child Grid:
<UserControl ... >
<Grid Name="mainPanel" HorizontalAlignment="Stretch" Width="500" >
<Image Source="{x:Bind Image}" HorizontalAlignment="Stretch" />
<TextBlock Text="{x:Bind Headline}" />
</Grid>
</UserControl>
And then back in the MainPage.xaml, I change the DataTemplate to reference the new FeaturedItemControl:
<GridView Name="FeaturedItems" Grid.Row="0">
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:NewsItem">
<local:FeaturedItemControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
However, I get the error message for both Image and Headline properties: Invalid binding path 'Headline': Property 'Headline' can't be found on type 'FeaturedItemControl'.
I've tried a few things but am flailing just throwing code at the problem without understanding what I'm doing. Any help would be greatly appreciated.
Thank you for your kind attention.
Using Depechie's answer, I formulated this little cheat cheat for posterity:
Do note that you MUST use this technique to utilize the VisualStateManager with items inside your data bound controls' (GridView, ListView) data templates.
1) Create a User Control.
2) Cut the content of the DataTemplate in your page and paste it into the User Control replacing the template's Grid.
3) Reference the User Control from inside the Data Template:
4) Modify the contents of the User Control changing x:Bind statements to utilize object.property notation:
<UserControl>
<StackPanel>
<Image Source="{x:Bind NewsItem.LeadPhoto}" />
<TextBlock Text="{x:Bind NewsItem.Headline}" />
<TextBlock Text="{x:Bind NewsItem.Subhead}" />
</StackPanel>
</UserControl>
5) Add this in the User Control's Code Behind:
public Models.NewsItem NewsItem { get { return this.DataContext as Models.NewsItem; } }
public ContactTemplate()
{
this.InitializeComponent();
this.DataContextChanged += (s, e) => Bindings.Update();
}
Well it's possible to use x:Bind in user controls, but you'll need to add some extra code behind.
I encountered the same problem in my project, you can see the result here : https://github.com/AppCreativity/Kliva/tree/master/src/Kliva/Controls
So what you need to do is, create a property in the code behind of your user control that points to the correct DataContext.
If you do that, you can use properties of that DataContext in the xaml of your control: for example:
Do note that in the constructor of your control you do need to add: DataContextChanged += (sender, args) => this.Bindings.Update(); because the datacontext will change depending on the page where your control is used!
Then on the page where you are placing this control, you'll also need to do the same to enable the x:bind to work.
You'll see this in my example on the MainPage.DeviceFamily-Mobile.xaml and MainPage.xaml.cs files.
Hope this helps.
x:Bind isn't really hierarchical like Binding/DataContext is. Additionally when you're not directly inside a DataTemplate (such as inside your user control) the object that x:Bind tries to use is 'this' rather than 'this.DataContext'. My current line of thinking on how to solve this sort of issue is to try not to use UserControls anywhere. Instead preferring DataTemplates contained within a ResourceDictionary. There are some pretty strong caveats to this approach though, you will for example crash the xaml compiler if you use x:Bind inside a data template that was created from the ResourceDictionary item template (add new item). you can find a pretty complete example here https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlBind its important to note in the sample where they show the ResourceDictionary being used that its not actually just a ResourceDictionary.xaml its also a ResourceDictionary.xaml.cs (this is where the generated code from x:Bind ends up)
Another option is to add Headline and Image as properties on your user control and x:Bind them from the template, then inside the user control x:Bind as you are currently doing, but now the x:Bind generated path 'this.Headline' will exist. Unfortunately the order things are actually bound means that the x:Bind's you have inside your user control will have to be OneWay rather than the default OneTime. this is because x:Bind OneTime does the bind inside the InitializeComponent call, and any set of properties/DataContext stuff doesn't get done until after that has already run.
So to sum this up, you have two options, use data templates everywhere, or bind to properties that are directly on the user control.

Empty LongListSelector has infinite length

I have a LongListSelector which is inside a StackPanel. when this LLS is empty, it has infinite length and elements which are at the bottom of it can't be seen.
<StackPanel Orientation="Vertical">
<phone:LongListSelector>
</phone:LongListSelector>
</StackPanel>
but when I set it's ItemsSource, it gets fine. I tried assigning it's VerticalAlignment to top, but didn't solved the issue
How to make it's size not fill the form?
(I've edited this post to make it better)
First of all lets describe the problem you have, to do it we will use:
PROBLEM: infinite length of LongListSelector (LLS)- to be honest, it isn't a problem and it works like it should. Because LLS can have many many items and can be very long as its name says. The problem is that you use it in StackPanel without fixing its Height.
SOLUTIONS:
The first is very simple - just set the height of LLS. You will be sure that what should be below LLS, will be there. Like #Chris W mentioned - using LLS in StackPanel is not most forunate and can cause many problems - so avoid it.
<StackPanel Orientation="Vertical">
<phone:LongListSelector Height="300"/>
<TextBlock Text="Something/>
</StackPanel>
The most elegant and the best solution (also what #Chris W suggested) - to put your LLS into Grid. That way has many advantages and with Rowdefinitions your program will be independent of Phone resolution - all your controls will be there, were they should be.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<phone:LongListSelector Width="100" Grid.Row="0"/>
<TextBlock Text="Something" Grid.Row="1"/>
</Grid>
The third solution is not as good ad previous, but shows other way to manage your Controls. You can override the way LLS is measured. But with this method you have to watch out for example: it will work ok with the problem, unless you add so many items that your Controls will be pushed off the screen. Also you will have to watch out for this.Width, which has to be defined. So many additional conditions you have to check, of course you can add more modifications and it will work, but as I said it's not as good as previous solutions.
namespace Extensions
{
public class LongListSelectorEx : LongListSelector
{
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
if (this.ItemsSource == null)
return new System.Windows.Size(this.Width, 0);
if (this.ItemsSource.Count <= 0)
return new System.Windows.Size(this.Width, 0);
return base.MeasureOverride(availableSize);
}
}
}
In your xaml you have to add:
<phone:PhoneApplicationPage
// something before
xmlns:common="clr-namespace:Extensions"
// other things
>
<StackPanel Orientation="Vertical">
<common:LongListSelectorEx Width="200"/>
<TextBlock Text="Something/>
</StackPanel>

How to set a background to a textBlock that changes its content

Background
I'm trying to put a textBlock control at the bottom of the screen (with a small margin below it), and I also wish to set a background for it, so that no matter what is shown behind the textBlock, it will be easy to read.
On Android, you could simply set the background to it, and tell it to have the width and height to be WRAP_CONTENT, so that it will take only the space it needs, but I can't find a similar thing on WP8.
Current status
This is the xaml I've created:
...
<Grid >
<Image x:Name="fullScreenImage" Stretch="Fill"
Visibility="Collapsed" />
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Bottom"
Margin="0,0,0,200" FontSize="40" x:Name="pictureLabel" TextWrapping="Wrap"
Foreground="#ff000000" />
</Grid>
The problem
Since the textBlock doesn't have a background property, I had to use something that wraps it. However, since its content changes dynamically, I can't simply set a size for it.
The question
For now, I would like to simply set its background color.
I would also very appreciate if it would be possible to use a rounded corners rectangle for the background, or a 9-patch image.
How can I achieve setting a background for the textBlock?
The solution is very simple. Just set the HirizontalAlignment to Left. Hope this will work in your case.
<StackPanel HorizontalAlignment="Left">
<Border Background="#66FFFFFF">
<TextBlock/>
</Border>
</StackPanel>
According to this question (which is for Silverlight - but is xaml nonetheless), there is no way to explicitly set a background color for a TextBlock. Your best bet is to wrap your TextBlock in a Grid or Border.
If a Grid does not work, this article suggests a Border will do the trick:
A simple border will do, and by not setting its width and height
properties, it will shrink/grow based on the size of the TextBlock.
I've come up with the next solution , which works quite fine , but what i really would like to also have is a way to set a min font and max font size , so that if there is a single word , the font might need to be of some size, and if the text is too long , the font will be of a smaller size , all in a dynamic way.
code:
label.Text = label;
label.Measure(new Size(RenderSize.Width, RenderSize.Height));
border.Width = label.DesiredSize.Width + border.Padding.Left + border.Padding.Right + border.BorderThickness.Left + border.BorderThickness.Right;
border.Height = label.DesiredSize.Height + border.Padding.Top + border.Padding.Bottom + border.BorderThickness.Bottom + border.BorderThickness.Top;
and the xaml:
<Border BorderBrush="#ff000000" BorderThickness="2" CornerRadius="8" Visibility="Collapsed" Padding="5" Background="#bfff0000" Margin="10,0,10,200" VerticalAlignment="Bottom" x:Name="border">
<StackPanel>
<TextBlock FontSize="40" x:Name="pictureLabel" TextWrapping="Wrap" Foreground="#ff000000" />
</StackPanel>
</Border>

Binding events to buttons in an ItemsControl

I have a windows phone 7 app with some xaml that looks like:
<Grid x:Name="ContentGrid" Grid.Row="1">
<ItemsControl x:Name="MyView" ItemTemplate="{StaticResource MyInner}"/>
</Grid>
The item template here looks like:
<DataTemplate x:Key="MyInner">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{StaticResource MyInner_Item}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
And finally, the MyInner_Item template looks like:
<DataTemplate x:Key="MyInner_Item">
<Button x:Name="MyButton">
<Button.Template>
<ControlTemplate>
<Border HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="myborder">
<Image Source="{Binding Path=ImageUri}" Width="{Binding Path=Width}" Height="{Binding Path=Height}" Stretch="Fill" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
So, it's an ItemsControl, which contains an ItemsControl, which contains buttons. This essentially creates a 2D array of buttons.
What I want to do is add an event handler to the Click event of the buttons.
Here's the catch: the code that sits behind this is written in F#. I can't, to the best of my knowledge, specify my event handler in the XAML, as F# doesn't talk to WPF in any nice way. So I need to add my event handler(s) manually in code.
Is there an easy way of doing this?
Currently, I have some F# which looks like:
let myView : ItemsControl = this?MyView
do myView.ItemsSource <- viewModel.BoardData
Here, the BoardData is a list of lists.
I was wondering if it's possible to loop through the controls in the ItemsControl, to add my event handlers? I'm having a bit of trouble doing this though. For example, in the following:
let container = myView.ItemContainerGenerator.ContainerFromItem(myView.Items.[0])
...sets container to null. In fact, all the methods I've tried from myView.ItemContainerGenerator returns null.
So, how should I go about attaching my event handler, so that I can respond to the buttons being clicked?
I have not done any Windows 7 Phone development, but I have done plenty of XAML + Silverlight development with C# and now I'm getting into doing some F# development. The approach I would take is by not using event handler's at all. Since you are using a button, make a class that derives from ICommand and add that type as a public property on your ViewModel so you could bind it to the Command property of the button. The benefits of using the ICommand interface over event handlers is that you could also have a condition on when the button is enabled to do the action.
Also, take notice that when you are doing binding expressions within (i.e. ItemTemplate) items in an ItemsControl control, the scope of what properties you can bind to are reduced to the properties of the current item. So all of the properties of the ViewModel are out of scope, unless you specify it fully i.e. <Button Command={Binding Source=ViewModel, Path=Property1.Property2.etc} />. Let me know if this helped.