I have a canvas and I would love to bind some points there on specified XY location. However, I can't achieve this with ItemsControl. I found some solutions but I guess they are not for Windows Phone.
The XAML I'm using is:
<ItemsControl ItemsSource="{Binding Path=Nodes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=XPos}" />
<Setter Property="Canvas.Top" Value="{Binding Path=YPos}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
And I get: "The member "ItemContainerStyle" is not recognized or is not accessible"
How to do that kind of binding in other way? I have name of object, and X/Y values and I want to bind is as a some kind of pushpin
I encountered the same issue but solved it using a TriggerAction instead. You can use the System.Windows.Interactivity if you have the Blend SDK. The dll is located in
c:\Program Files\Microsoft SDKs\Expression\Blend\Silverlight\v4.0\Libraries\
System.Windows.Interactivity.dll`
Then, by using your previous xaml code I can set the datatemplate as such:
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Stroke="Red" Width="2" Height="2">
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<tr:SetCanvasPropertiesAction Left="{Binding X}" Top="{Binding Y}" />
</ia:EventTrigger>
</ia:Interaction.Triggers>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
Note the ia:Interaction prefix, from the interactivity dll mentioned before. You load it with
xmlns:ia="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
in the top of your xaml file.
The tr prefix is for including my own class, which looks like this:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace Presentation.Triggers {
public class SetCanvasPropertiesAction : TriggerAction<DependencyObject> {
public static readonly DependencyProperty LeftProperty =
DependencyProperty.Register("Left", typeof(double), typeof(SetCanvasPropertiesAction), new PropertyMetadata(default(double)));
public static readonly DependencyProperty TopProperty =
DependencyProperty.Register("Top", typeof(double), typeof(SetCanvasPropertiesAction), new PropertyMetadata(default(double)));
public double Top {
get { return (double)GetValue(TopProperty); }
set { SetValue(TopProperty, value); }
}
public double Left {
get { return (double)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
}
protected override void Invoke(object parameter) {
UIElement presenter = (UIElement)VisualTreeHelper.GetParent(AssociatedObject);
Canvas.SetLeft(presenter, Left);
Canvas.SetTop(presenter, Top);
}
}
}
Two things to note with the Invoke method. The first is AssociatedObject, that gets resolved to the Ellipse since the trigger is nested under it in the xaml. The second thing is the VisualTreeHelper, which gets the parent to the ellipse. This is the ContentPresenter on which we want to set the attached properties of the canvas.
It might look like it's more complicated, but as with everything else in mvvm, you can reuse it in xaml and you don't have to copy and paste code-behind code everywhere.
Finally I did it in C# only using foreach.
foreach (var InsidePoints in _inside)
{
double left = InsidePoints.xaxis * Layer_3.Width;
double top = InsidePoints.yaxis * Layer_3.Height;
Image pinImage = new Image();
pinImage.Name = InsidePoints.id.ToString();
pinImage.Source = (ImageSource)new ImageSourceConverter().ConvertFromString("icons/inside_pin.png");
pinImage.Tap += new EventHandler<System.Windows.Input.GestureEventArgs>(pinImage_Tap);
//pinImage.Hold += new EventHandler<System.Windows.Input.GestureEventArgs>(pinImage_Hold);
Layer_3.Children.Add(pinImage);
Canvas.SetLeft(pinImage, left);
Canvas.SetTop(pinImage, top);
}
Related
.Net Maui page with CarouselView and list of cards created via data binding to the collection of items in ViewModel (VM). I'm looking for ways to animate control inside CarouselView by some property in VM set to a particular value. Animation should be done in c# code (code-behind, trigger action, behavior etc.), not by xaml. Not sure how to properly implement that. This is what I considered as possible solutions:
Declare event in VM and subscribe for it in code-behind. Works very well for non-template controls, but with CarouselView which consists of collection Card controls described inside DataTemplate I need to find that particular active control only, let's say Label that I want to animate. Not sure how to find it, there are one instance of it per each item in VM collection, but even if I do it does not look to be a good MVVM oriented design.
My big hope was on TriggerAction<Label> (given I want to animate Label), but then problem is TriggerAction seems to only work inside EventTrigger which only catches xaml control events, not VM events. What can catch VM events and property changes is DataTrigger, but it does not allow to have TriggerAction<T> declared inside on the other hand. Not sure why there is such limitation in .Net Maui, I wish I had some mix of both.
Behaviors, - as with triggers not sure how to run them by any property change or event declared in VM
// ViewModel (mvvm community toolkit is used):
public partial class CollectionOfItems : ObservableObject
{
public Item[] Items {get; set;}
}
public partial class Item : ObservableObject
{
[ObservableProperty]
public string _name;
// Setting this to false should trigger Label animation
[ObservableProperty]
public bool _isInvalid;
}
...
<CarouselView ItemsSource="{Binding Items}">
<CarouselView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Name}">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding IsInvalid}" Value="True">
<!-- I wish I was able to trigger InvalidDataTriggerAction from here, but it's not compatible with DataTrigger -->
</DataTrigger>
</Label.Triggers>
</Label>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
...
// Animation should be implemented in c#, not XAML
// Should be triggered by IsInvalid=false, but I do not know how:
public class InvalidDataTriggerAction : TriggerAction<Label>
{
protected override void Invoke(Label label)
{
label.TranslateTo(-30, 0, 100);
label.TranslateTo(60, 0, 100);
label.TranslateTo(-30, 0, 100);
}
}
Alright, I've found a way to do it through bindable properties inside behaviors. Happened to be bit more complicated than I expected, but it works. Unfortunately .NET Maui does not provide a better more intuitive way to do that, I guess it's the area for improvement.
Here's the code:
namespace View.Behaviors;
public class AnimateWrongAnswerBehavior : BaseBehavior<VisualElement>
{
public static readonly BindableProperty ShouldAnimateProperty =
BindableProperty.CreateAttached(
"ShouldAnimate",
typeof(bool),
typeof(AnimateWrongAnswerBehavior),
false,
propertyChanged: OnShouldAnimateChanged);
public static void SetShouldAnimate(BindableObject view, VisualElement value) =>
view.SetValue(ShouldAnimateProperty, value);
static async void OnShouldAnimateChanged(BindableObject bindable, object oldValue, object newValue)
{
if ((bool)newValue)
{
await Animate((VisualElement)bindable);
}
}
/// <summary>
/// Implemenation of Animation logic
/// </summary>
static private async Task Animate(VisualElement elementToAnimate)
{
await elementToAnimate.TranslateTo(-30, 0, 100);
await elementToAnimate.TranslateTo(60, 0, 100);
await elementToAnimate.TranslateTo(-30, 0, 100);
}
}
Then you can bind animation to any visual element like Frame:
<ContentPage
xmlns:bh="clr-namespace:View.Behaviors" ...>
<Frame bh:AnimateWrongAnswerBehavior.ShouldAnimate="{Binding IsInvalid}">
</ContentPage>
Just like you said, the control in the datatemplate is hard to access. So I have done a sample to test the TranslateX property of the Label. And I found that label.TranslateTo(60, 0, 100); had the same effect as the label.TranslateX = 60.
According to this, you can create a variable in the Item and binding it to the label in the DataTemplate. And change the item's value in the page.cs. And you can also use the DataTrigger to set the value of label.TranslateX.
Please note that TranslateTo is awaitable and might need the async/await pattern for the element to be animated, especially if more than one TranslateTo is invoked.
TriggerAction with async/await added:
public class TriggerActionTranslateTo : TriggerAction<VisualElement>
{
protected override async void Invoke(VisualElement sender)
{
await sender.TranslateTo(0, 0, 250);
await sender.TranslateTo(30, 0, 250);
await sender.TranslateTo(0, 0, 250);
}
For some reason async/await is not needed with only one TranslateTo and this will animate:
protected override void Invoke(VisualElement sender)
{
sender.TranslateTo(30, 0, 250);
}
XAML:
<Label.Triggers>
<DataTrigger
TargetType="Label"
Binding="{Binding Property}"
Value="True">
<DataTrigger.EnterActions>
<ns:TriggerActionTranslateTo/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
...
Here is the XAML that I have:
<Frame CornerRadius="1" HasShadow="false" Margin="10"
BackgroundColor="White" BorderColor="Silver" Padding="0" >
I saw on the Google Translate that's on iOS that they use something like this kind of a frame to surround different rows in settings. However they have a different border color on the top and bottom.
Does anyone know if there is a way to do the with a frame?
You could achieve that with a component, like this
BorderEntryComponent.xaml
<?xml version="1.0" encoding="UTF-8"?>
<StackLayout
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="X.Y.Z.BorderEntryComponent"
Spacing="0">
<BoxView
x:Name="TopBorder"
HeightRequest="2"
HorizontalOptions="FillAndExpand"
VerticalOptions="EndAndExpand" />
<Entry x:Name="Entry" />
<BoxView
x:Name="BottomBorder"
HeightRequest="2"
HorizontalOptions="FillAndExpand"
VerticalOptions="EndAndExpand" />
</StackLayout>
And, in your BorderEntryComponent.xaml.cs
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
namespace X.Y.Z
{
public partial class BorderEntryComponent : StackLayout
{
public static readonly BindableProperty TopColorProperty =
BindableProperty.Create(nameof(TopColor), typeof(Color), typeof(BorderEntryComponent), default(Color), BindingMode.OneWay);
public static readonly BindableProperty BottomColorProperty =
BindableProperty.Create(nameof(BottomColor), typeof(Color), typeof(BorderEntryComponent), default(Color), BindingMode.OneWay);
public UnderlineEntryComponent()
{
InitializeComponent();
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == TopColorProperty.PropertyName)
{
this.TopBorder.Color = TopColor;
}
else if (propertyName == BottomColorProperty.PropertyName)
{
this.BottomBorder.Color = BottomColor;
}
}
public Color TopColor
{
get => (Color)GetValue(TopColorProperty);
set => SetValue(TopColorProperty, value);
}
public Color BottomColor
{
get => (Color)GetValue(BottomColorProperty);
set => SetValue(BottomColorProperty, value);
}
}
}
Then, you just do this on your .xaml
<components:UnderlineEntryComponent
TopColor = "Blue"
BottomColor = "Black" />
You can read more on Bindable Properties here
AFAIK, you don't have a built in option for what you are looking for.
You could play around by drawing multiple frames on top of each other with different colors and properties, but it is a bit too "hacky" for my taste.
I suggest you create a Custom Render for your own Frame control. This way, you will be able to draw the frame however you want and reuse your control in any other place.
first of all sorry for my english.
I am developing a WP7 app, and I still haven't completely understood the data binding structure. I have a page that has some data obtained through data binding. Data is generated within the .cs, and it works fine.
But on another page I have some data that is obtained from data binding too, but I want it to come from a UI input text instead. It's simple, just a textbox and a textblock, so the user writes something on the textbox and so it shows up on the textblock that's on the same page. But it's not working, the textblock remains empty.
It's something like this:
<TextBox Name="TestInput">
<TextBlock Text="{Binding TestText}">
Above is what's on the XAML.
public partial class NewItem : PhoneApplicationPage
{
public String TestText { get; set; }
public NewItem()
{
InitializeComponent();
TestText = "TestInput.Text";
}
}
And this above is what's on the C#.
BUT!! It doesn't end here. Since the textblock wasn't showing anything, I ended desperately trying to assign some plain String to the TestText property. Like this:
TestText = "HELLO WORLD";
But when the app starts and the page loads, the textblock shows nothing. I just don't understand what am I missing, or doing wrong.
I will appreciate if someone could clear me up the databinding structure, or at least explain me what did I do wrong so I can figure it out myself.
Thanks in advance guys!
You have to assign the DataContext before you want the binding effect.So whenever there is a change in the text box, you write the code in the textchanged event.
this.DataContext=TestText
And one more change that you need to perform is that you are not actually setting the property.It should be like
TestText=TestInput.Text
for your understanding of binding i am putting a simple working example..just follow this..
this is you page on which you bind the data of textbox to someproperty textboxText..when you finished writing in this textbox.then all the writtentext automatically come ino this property. and this property is also binded to textbloack so when your textbloack got focus it will got to the get of the property and automatically fill it.
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Horizontal" >
<TextBox x:Name="testTextbox" Height="50" Width="200" Text="{Binding TextboxText,Mode=TwoWay}" />
<TextBlock x:Name="testTextblock" Height="50" Width="1000" Text="{Binding TextboxText,Mode=OneWay}" Foreground="White" />
</StackPanel>
</Grid>
this is your page.cs class in which i have also shown you how to implement inotifyproperty changed..it will help you in future..
public sealed partial class MainPage : Page,INotifyPropertyChanged
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
private string _TextboxText;
public string TextboxText
{
get
{
return _TextboxText;
}
set
{
_TextboxText = value;
FirePropertyChanged("TextboxText");
testTextblock.UpdateLayout();
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
<TextBlock Name="currency" TextWrapping="Wrap" VerticalAlignment="Center"/>
<TextBlock Margin="5,0" Text="{Binding Text, ElementName=currency" TextWrapping="Wrap" VerticalAlignment="Center" FontSize="22" />
I am using the above code for binding property of one field to another in my WP7 application.
i want to do the similar task from back-end. any suggestions??
Bindings are working in a specified data context. You can set the data context of your layout root to the page instance, then you can bind to any of your properties. (DataContext is inherited through the child FrameworkElements.) If you want your binding to update its value whenever you change your property from code, you need to implement INotifyPropertyChanged interface or use Dependency properties.
<Grid x:Name="LayoutRoot">
<TextBox Text="{Binding Test, Mode=TwoWay}" />
</Grid>
public class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
private string test;
public string Test
{
get { return this.test; }
set
{
this.test = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Test"));
}
}
public MainPage()
{
InitializeComponents();
LayoutRoot.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
}
This is a stupid example since you can access your TextBox any time from MainPage, this has much more sense if you are displaying model objects with DataTemplates.
(I typed this on phone, hope it compiles..)
i got my solution as: var b = new Binding{ElementName = "currency", Path = new PropertyPath("Text")}; Textblock txt = new TextBlock(); txt.SetBinding(TextBlock.TextProperty, b);
In the handler for the Completed event of a Storyboard, how do i get the element that the storyboard was being applied to?
My Storyboard is part of an ItemTemplate:
<ListBox x:Name="MyListBox" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="Container" Height="30" >
<Grid.Resources>
<Storyboard x:Name="FadeOut" BeginTime="0:0:7" Completed="FadeOut_Completed">
<DoubleAnimation From="1.0" To="0.0" Duration="0:0:3" Storyboard.TargetName="Container" Storyboard.TargetProperty="Opacity" />
</Storyboard>
</Grid.Resources>
[...snip...]
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
in the Completed event i want to grab the grid called Container so that i can do nasty things with its DataContext. Can this be done, or am i going about it the wrong way?
Thanks :)
The answer to this is that it is not possible - not in Silverlight 3 anyway.
Using a debugger i was able to find a private property of the Storyboard that when i walked it up the object tree i got to the containing template item - however i couldn't touch this from code using reflection due to the security restrictions placed upon silverlight apps (this may well be possible in WPF though).
My eventual solution involved using a Dictionary<Storyboard, Grid>, and a couple of event handlers. With the template i attached a Loaded handler, this means my handler gets called everytime an instance of the template is created and loaded (i.e. for every data item that is bound to the listbox). At this point, i have a reference to the physical instance of the template, so i can search its children for the storyboard:
private void ItemTemplate_Loaded(object sender, RoutedEventArgs e)
{
Storyboard s = getStoryBoard(sender);
if (s != null)
{
if (!_startedStoryboards.ContainsKey(s))
_startedStoryboards.Add(s, (Grid)sender);
}
}
private Storyboard getStoryBoard(object container)
{
Grid g = container as Grid;
if (g != null)
{
if (g.Resources.Contains("FadeOut"))
{
Storyboard s = g.Resources["FadeOut"] as Storyboard;
return s;
}
}
return null;
}
private Dictionary<Storyboard, Grid> _startedStoryboards = new Dictionary<Storyboard, Grid>();
Then when the storyboard's Completed event is fired, i can easily use this dictionary as a lookup to retrieve the item template it was hosted within, and from there i can get the DataContext of the item template and do the nasty things i planned:
private void FadeOut_Completed(object sender, EventArgs e)
{
if (_startedStoryboards.ContainsKey((Storyboard)sender))
{
Grid g = _startedStoryboards[(Storyboard)sender];
if (g.DataContext != null)
{
MyDataItem z = g.DataContext as MyDataItem;
if (z != null)
{
... do my thing ...
}
}
}
}
[Note: this code has been sanitized for public viewing, excuse any small discrepancies or syntactical errors you may spot]