Change property of an item inside a GridView? - xaml

I have a GridView, with this item template (XAML):
<DataTemplate x:DataType="local:MyClass">
<TextBlock Text="{x:Bind MyString}"/>
<Button Command="{x:Bind command}"/>
</DataTemplate>
Here's MyClass (C++/CX)
public ref class MyClass sealed
{
public:
MyClass();
property Platform::String^ MyString
{
String^ get()
{
return mystring;
}
}
property ICommand^ command = ref new DelegateCommand(ref new ExecuteDelegate(this, &implementcommand), nullptr);
private:
String^ mystring;
void implementcommand(Platform::Object^ param);
}
The DelegateCommand function is pulled from this sample without modifications: https://code.msdn.microsoft.com/windowsapps/Command-binding-inside-3cef5eea
Right now, if I click any of the buttons in the gridview, it will call implementcommand. However, I want to have implementcommand do something with the TextBlock control, for example changing the content of the text. From any function inside the mainpage class, and if it wasn't inside the gridview, it would be as simple as textBlock->Text = ref new String("hello"); However, since the control is inside the gridview, and the implementcommand function is in MyClass, not MainPage, I don't know how to do this.

In the TextBlock in your DataTemplate you bind the Text to a MyString property which (judging by the bindings I see) exists in the same class as the command.
What you need to do is
In your implementcommand change the value of MyString
Call
Bindings.Update()
There is also another way (the classic way before x:Bind was available)
Change the binding mode in the TextBlock to OneWay
In your class implement INotifyPropertyChanged
In your implementcommand change the value of MyString and raise INotifyPropertyChanged event.

Related

UWP "compound" binding (binding Data.Name instead of Name)

I have a simple page with text block and a button. I want to change the text when I press the button. But using a text from a Data.Name property of the Page.
I know I can have this simpler (having just Name instead of Data.Name), but I need Data.Name, don't ask why.
For this I have a class DataType which has the Name property and object named Data of that class. I want to have Data inside this Page, and bind the text to the Data.Name property.
When I click on the button, nothing happens, the question is how canI make this work?
XAML:
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Data.Name, Mode=OneWay}"/>
<Button Grid.Row="1" Content="Change" Click="Button_Click"/>
</Grid>
</Page>
Class DataType
public ref class DataType: public INotifyPropertyChanged {
public:
property String^ Name
{
String^ get() {
return m_Name;
}
void set(String^ value) {
m_Name = value;
OnPropertyChanged("Name");
}
}
virtual event PropertyChangedEventHandler^ PropertyChanged;
private:
void OnPropertyChanged(Platform::String^ propertyName)
{
PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
}
String^ m_Name;
};
Class MainPage
public ref class MainPage sealed: public INotifyPropertyChanged
{
public:
MainPage();
property DataType^ Data {
DataType^ get() {
return m_Data;
}
void set(DataType^ value) {
m_Data = value;
OnPropertyChanged("Data");
}
}
virtual event PropertyChangedEventHandler^ PropertyChanged;
private:
void OnPropertyChanged(Platform::String^ propertyName)
{
PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
}
void Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
Data->Name = rand() & 1 ? "Test1" : "Test2";
OnPropertyChanged("Data");
}
DataType^ m_Data;
};
In UWP, there are x:Bind and Binding markup extension, they have some differences when you use them. You can learn the details from the document of above two links.
Now we will discuss the reason that caused your above issue.
In your xaml, you use the Binding markup extension to bind the property path, since Binding uses the DataContext as a default source. Simplely to say, when you use Binding property path, you bind the DataContext.Property path, you just need use the Bind source object's property but not need to specify the Source data object on the xaml. As the introduction of Traversing an object graph:
"{Binding Path=Customer.Address.StreetAddress1}"
Here's how this path is evaluated:
The data context object (or a Source specified by the same Binding) is searched for a property named "Customer".
The object that is the value of the "Customer" property is searched for a property named "Address".
The object that is the value of the "Address" property is searched for a property named "StreetAddress1".
See the Property-path syntax for the details.
So your code will work just binding the Name property and set the DataContext. (Note that: your MainPage class don't need to implement the INotifyPropertyChanged interface.)
<TextBlock Grid.Row="0" Text="{Binding Name, Mode=OneWay}"/
And
this->DataContext = Data;
Also note: If you're using Visual C++ component extensions (C++/CX) then, because we'll be using {Binding}, you'll need to add the BindableAttribute attribute to the DataType class.
[Windows::UI::Xaml::Data::Bindable]
public ref class DataType sealed : public INotifyPropertyChanged {
...
}
On the other hand, you can use the x:Bind instead of the Binding, since x:Bind don't use the DataContext as a default source—instead, it uses the page or user control itself. So it will look in the code-behind of your page or user control for properties, fields, and methods. To expose your view model to {x:Bind}, you will typically want to add new fields or properties to the code behind for your page or user control. For example: in a page, Text="{x:Bind Employee.FirstName}" will look for an Employee member on the page and then a FirstName member on the object returned by Employee.
The issue is that DataType properties isn't visible from XAML, because "DataType.h" isn't included in "pch.h"
Once I included it in precompiled header, everything worked.
BTW, looks like Binding is not checking for type visibility from XAML or whatever, but x:Bind does. Using x:Bind, the compiler complains about unknown Data.Name, and that allowed me to figure out the problem.

Ignore the Binding initialization

The inital problem is coming from a personal project about the polyline of the Xamarin.Forms.Map where the initialization is realized by a binding from the XAML part..
Let me be clear by an example :
I have an object CustomMap.cs which inherit from Xamarin.Forms.Map (This file is in the PCL part -> CustomControl/CustomMap.cs)
public class CustomMap : Map, INotifyPropertyChanged
{
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(List<string>), typeof(CustomMap), null);
public List<string> PolylineAddressPoints
{
get { return (List<string>)GetValue(PolylineAddressPointsProperty); }
set
{
SetValue(PolylineAddressPointsProperty, value);
this.GeneratePolylineCoordinatesInner();
}
}
// ...
}
As you can see, I have a bindable property with an assessor and the XAML doesn't seem to use this assessor..
So the MainPge.xaml part of the page, where the control is called, looks like that:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:control="clr-namespace:MapPolylineProject.CustomControl;assembly=MapPolylineProject"
x:Class="MapPolylineProject.Page.MainPage">
<ContentPage.Content>
<control:CustomMap x:Name="MapTest" PolylineAddressPoints="{Binding AddressPointList}"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
</ContentPage>
The MainPge.xaml.cs part:
public partial class MainPage : ContentPage
{
public List<string> AddressPointList { get; set; }
public MainPage()
{
base.BindingContext = this;
AddressPointList = new List<string>()
{
"72230 Ruaudin, France",
"72100 Le Mans, France",
"77500 Chelles, France"
};
InitializeComponent();
//MapTest.PolylineAddressPoints = AddressPointList;
}
}
So, everything is fine if I edit the PolylineAddressPoints from the object instance (if the commented part isnt' commented..), but if I init the value from the XAML (from the InitializeComponent();), it doesn't work, the SetValue, in the Set {}, of the CustomMap.PolylineAddressPoints, isn't called..
I then searched on the web about it and get something about the Dependency Properties? or something like that. So I tried some solutions but, from WPF, so some methods, such as DependencyProperty.Register();. So yeah, I can't find the way to solve my problem..
I also though about something, if DependencyProperty.Register(); would exists in Xamarin.Forms, then it means I would have to do it for each values? Because, if every value has to be set by a XAML binding logic, it would not work, I would have to register every value, doesn't it?
I'm sorry if I'm not clear, but I'm so lost about this problem.. Please, do not hesitate to ask for more details, thank in advance !
Finaly, the initial problem is that I'm trying to set a value of an object/control, from the XAML. Doing this by a Binding doesn't work, it seems like it ignored.. However, it does work if I do the following:
MapTest.PolylineAddressPoints = AddressPointList;
There are multiple questions in this:
Why is the property setter never called when using Xaml ?
Am I properly defining my BindableProperty ?
Why is my binding failing ?
Let me answer them in a different order.
Am I properly defining my BindableProperty ?
The BindableProperty declaration is right, but could be improved by using an IList<string>:
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(IList<string>), typeof(CustomMap), null);
but the property accessor is wrong, and should only contains this:
public IList<string> PolylineAddressPoints
{
get { return (IList<string>)GetValue(PolylineAddressPointsProperty); }
set { SetValue(PolylineAddressPointsProperty, value); }
}
I'll tell you why while answering the next question. But you want to invoke a method when the property has changed. In order to do that, you have to reference a propertyChanged delegate to CreateBindableProperty, like this:
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(IList<string>), typeof(CustomMap), null,
propertyChanged: OnPolyLineAddressPointsPropertyChanged);
And you have to declare that method too:
static void OnPolyLineAddressPointsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
((CustomMap)bindable).OnPolyLineAddressPointsPropertyChanged((IList<string>)oldValue, (IList<string>)newValue);
}
void OnPolyLineAddressPointsPropertyChanged(IList<string> oldValue, IList<string> newValue)
{
GeneratePolylineCoordinatesInner();
}
Why is the property setter never called when using Xaml ?
The property, and the property accessors, are only meant to be invoked when accessing the property by code. C# code.
When setting a property with a BindablePrperty backing store from Xaml, the property accessors are bypassed and SetValue() is used directly.
When defining a Binding, both from code or from Xaml, property accessors are again bypassed and SetValue() is used when the property needs to be modified. And when SetValue() is invoked, the propertyChanged delegate is executed after the property has changed (to be complete here, propertyChanging is invoked before the property change).
You might wonder why bother defining the property if the bindable property is only used by xaml, or used in the context of Binding. Well, I said the property accessors weren't invoked, but they are used in the context of Xaml and XamlC:
a [TypeConverter] attribute can be defined on the property, and will be used
with XamlC on, the property signature can be used to infer, at compile time, the Type of the BindableProperty.
So it's a good habit to always declare property accessors for public BindableProperties. ALWAYS.
Why is my binding failing ?
As you're using CustomMap as both View and ViewModel (I won't tell the Mvvm Police), doing this in your constructor should be enough:
BindingContext = this; //no need to prefix it with base.
As you're doing it already, your Binding should work once you've modified the BindableProperty declaration in the way I explained earlier.

Setting the initial selected item when binding to a ListView's SelectedItem property

I have a Xamarin.Forms xaml page in which I am using a ListView to allow the user to pick a single item out of a list. I have bound the ListView's SelectedItem property to a property on my ViewModel and this works fine. As soon as the user changes the selected item the property in my viewmodel updates as well.
However, even though I initially set the property in my ViewModel to one of the values from the list, when the page loads the ListView's SelectedItem property is null, which in turn sets the ViewModel property to null as well.
What I need is the other direction, I want the ListView to initially select the item that i've set in the VM property.
I can hack together a solution by writing extra code in the code behind file to explicitly set the initial selected item, but this introduces additional properties and complexity and is quite ugly.
What is the correct way to set the initial selected item of a ListView who's selected item is bound to a viewmodel property?
-EDIT-
I was asked to provide the code that I'm using for my binding.
It's very simple, standard:
<ListView x:Name="myList" ItemsSource="{Binding Documents}" SelectedItem="{Binding SelectedDocument}">
the view model that is set as the binding context for the listview is instantiated before the page is created and looks like this:
public class DocumentSelectViewModel : ViewModelBase
{
private Document selectedDocument;
public List<Document> Documents
{
get { return CachedData.DocumentList; }
}
public Document SelectedDocument
{
get { return selectedDocument; }
set { SetProperty(ref selectedDocument, value);
}
public DocumentSelectViewModel()
{
SelectedDocuement = CachedData.DocumentList.FirstOrDefault();
}
}
SetProperty is a function which simply rasies the INotifyPropertyChanged event if the new value is different from the old one, classical binding code.
I am a little rusty on XAML but don't you need to make the binding two-way?
E.G.
{ Binding SelectedDocument, Mode=TwoWay }
As long as the SelectedDocument property change raises the INotifyPropertyChanged event then you should get the desired effect.
If you replace
public DocumentSelectViewModel()
{
SelectedDocument = CachedData.DocumentList.FirstOrDefault();
}
By
public DocumentSelectViewModel()
{
SelectedDocument = Documents.FirstOrDefault();
}
Does it work for you ?
I had a similar problem that has been resolved this way...
You can use ctor DocumentSelectViewModel for set initial value. Honestly I dont like to make some job in ctor block but Xamarin.... You dont need DocumentSelectViewModel method. It will work.
public DocumentSelectViewModel ()
{
SelectedDocument = Documents[0]; //or any your desired.
}

How to access Pivot.TitleTemplate as UserControl?

Subj, how can i get it?
<controls:Pivot.TitleTemplate>
<DataTemplate>
<mainPivot:MyUserControl Name="MainPivotHeader"/>
</DataTemplate>
</controls:Pivot.TitleTemplate>
Tried to find it via VisualTreeFinders, but it sees only pivot item.
UserControl shows a picture, but it depends on user. During first initialization, it is empty, because user is not yet logged in. So, i'd like to force its update.
I can use mvvm light messaging, but i'm looking for self-sufficient components. This forcing is rare, so i dont want to use messaging here.
You should bind the Title property of the Pivot to a property on a ViewModel. Your DataTemplate would then have it's DataContext already set to that object. When you need to refresh, you call some method on that object.
Example
public class ViewModel : INotifyPropertyChanged
{
private MyTitleObject _titleObject;
public MyTitleObject TitleObject
{
get { return _titleObject; }
set
{
_titleObject = value;
OnPropertyChanged("TitleObject");
}
}
public void Refresh()
{
TitleObject = new MyTitleObject();
// or refresh values directly on the object
}
...
}
You xaml for your Pivot would need to following
<controls:Pivot Title="{Binding TitleObject}">
</controls:Pivot>
When you want to refresh, call the refresh on the viewmodel.

Command parameter in EventTrigger

I'm working on WinRT with MvmmCross v3 framework and Windows.UI.Interactivity.
I want a TextBox with an EventTrigger on the event TextChanged which launch a Command. And also, I want in CommandParameter the text of the textBox.
So I have this code
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding UpdateText}" CommandParameter="{Binding Text}"/>
</i:EventTrigger>
public ICommand UpdateText
{
get
{
return new MvxCommand<object>((textSearch) =>
{
// code...
});
}
}
But my textSearch parameter equals to "{Windows.UI.Xaml.Controls.TextChangedEventArgs}" with all of these properties NULL.
I Tried also to declare explicitly my ElementName in the binding like
CommandParameter="{Binding Path=Text, ElementName=tes}
But it failed too.
Thanks
Do you really need to handle TextChanged event? You could be notified of the changes by just binding to the Text property:
<TextBox Text="{Binding TextValue, Mode=TwoWay}" />
And then in the view model:
private string _textValue;
public string TextValue
{
get
{
return _textValue;
}
set
{
if (_textValue == value)
return;
_textValue = value;
OnTextChanged(value); // react to the changed value
}
}
EDIT:
There are two things you need to be aware of, if you want to get to the Text value from inside your Command:
First, you need to fix the CommandParameter binding. By using {Binding Text} you are actually trying to bind to a property in your view model, i.e. you would first need to bind the TextBox.Text property to the same view model property. As you've said in the comment, that's no good for you because you need the info on every change and not only on lost focus, so the value you get is not up to date enough.
The right approach would therefore be your second attempt, i.e. binding directly to the TextBox using the ElementName syntax. Unfortunately triggers are not a part of the visual tree therefore they don't get access to the XAML name scope (you can read more about it in this blog post). You can work around that by using NameScopeBinding from MVVMHelpers.Metro package:
<Behaviors:NameScopeBinding x:Key="MyTextBox"
Source="{Binding ElementName=MyTextBox}" />
Now you can make the ElementName binding work:
<i:InvokeCommandAction Command="{Binding UpdateText}"
CommandParameter="{Binding Source.Text, Source={StaticResource MyTextBox}}"/>
You still have the second problem. The Text value that you are binding to only updates on lost focus so you don't get the right value when handling TextChanged event. The solution is to bind to the TextBox itself:
<i:InvokeCommandAction Command="{Binding UpdateText}"
CommandParameter="{Binding Source, Source={StaticResource MyTextBox}}"/>
And then inside the command get the Text property directly from the TextBox:
private void OnUpdateText(object parameter)
{
var value = ((TextBox) parameter).Text;
// process the Text value as you need to.
}
EDIT 2:
To make the above code work with the view model being in a PCL, there a couple of approaches you could take.
The simplest hack would be to use reflection. Since it is available in PCL you could get to the Text property and read its value:
private void OnUpdateText(object parameter)
{
var textProperty = textSearch.GetType().GetProperty("Text");
var value = textProperty.GetValue(parameter, null) as string;
// process the Text value as you need to.
}
A cleaner solution would be to put the WinRT specific code into a separate assembly abstracted via an interface. You would first create an interface in the PCL library:
public interface IUiService
{
string GetTextBoxText(object textBox);
}
And modify view model to accept this interface in the constructor:
public ViewModel(IUiService uiService)
{
_uiService = uiService;
}
In the command handler you would than use the method in the interface:
private void OnUpdateText(object parameter)
{
var value = _uiService.GetTextBoxText(parameter);
// process the Text value as you need to.
}
You would implement that interface in a WinRT library:
public class UiService : IUiService
{
public string GetTextBoxText(object textBox)
{
var typedTextBox = textBox as TextBox;
return typedTextBox.text;
}
}
In the application you reference this library and pass the implementation to view model:
var viewModel = new ViewModel(new UiService);
My favorite approach is different: I'd create a Behavior exposing a Text property automatically updated every time TextChanged event is triggered:
public class TextChangedBehavior : Behavior<TextBox>
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(TextChangedBehavior),
new PropertyMetadata(null));
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += OnTextChanged;
Text = AssociatedObject.Text;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= OnTextChanged;
}
private void OnTextChanged(object sender, TextChangedEventArgs textChangedEventArgs)
{
Text = AssociatedObject.Text;
}
}
Now I could bind a TextValue property to this behavior and react to its change in the property setter as already suggested at the very beginning of this long answer:
<TextBox>
<i:Interaction.Behaviors>
<b:TextChangedBehavior Text="{Binding TextValue, Mode=TwoWay}" />
</i:Interaction.Behaviors>
</TextBox>