How to fire a command after clicking somewhere else? - xaml

So, I have a custom control, CusConA, that works basically like a textbox - you type amount of money that you need, and I have a button below, whom by getting clicked saves that amount(from CusConA) somewhere, and that is working fine.
But I want to try the same functionality basically by clicking anywhere on that page (something like OnBlur in asp.net), or to be precise, when my CusConA is not in focus anymore.
By doing what is shown with the --> in code, I achieved sort of a solution, this way when pressing anywhere, even if I never even tried to write an amount, the command is being executed.
So, to try to circle my question, I need this command to execute only after typing some amount, and clicking somewhere alse after. How can I do that?
<Frame
Margin="55,0"
Padding="0"
BorderColor="Blue"
CornerRadius="30">
<StackLayout Orientation="Horizontal">
<Label
Margin="10"
FontAttributes="Bold"
FontSize="20"
HorizontalTextAlignment="Center"
Text="RSD"
TextColor="Some text"
VerticalTextAlignment="Center" />
<customControls:CusConA
Margin="0,0,15,0"
HorizontalOptions="FillAndExpand"
Keyboard="Numeric"
Placeholder="0,00"
PlaceholderColor="Gray"
Text="Some text"
TextColor="Black" >
--> <customControls:CusConA.Behaviors>
<xct:EventToCommandBehavior EventName="Unfocused" Command="{Binding DoSomething}" ></xct:EventToCommandBehavior>
</customControls:CusConA.Behaviors>
</customControls:CusConA>
</StackLayout>
</Frame>

Can you change DoSomething to check whether the amount has been typed? Might involve adding a boolean property to your control:
bool CanExecute { get; set; }
Then have "amount" bound to a property whose setter sets CanExecute = true; or CanExecute = false;, depending on whether an amount has been typed. Something like:
string Amount
{
...
set {
_amount = value;
myControl.CanExecute = value.Count > 0;
}
}
Then change DoSomething body to
if (this.CanExecute) { ... }
Alternatively, other techniques can be used to have a change to Amount trigger a change to a property on myControl.
The essential points are:
Adding CanExecute property, so control can be told when it is valid to execute that command.
Using some technique to bind or trigger myControl.CanExecute change, from elsewhere.

I think you can use EventToCommandBehavior to achieve this function.
There is an example of an EventToCommandBehavior in the Xamarin.Forms samples (see here).
<ContentPage.BindingContext>
<focusapp:MyViewModel></focusapp:MyViewModel>
</ContentPage.BindingContext>
<StackLayout>
<Entry>
<Entry.Behaviors>
<Behaviors:EventToCommandBehavior
EventName="Unfocused"
Command="{Binding EntryUnfocused}" />
</Entry.Behaviors>
</Entry>
</StackLayout>
And define EntryUnfocused in your viewmodel.cs (e.g. MyViewModel) just as follows:
MyViewModel.cs
public class MyViewModel
{
public ICommand EntryUnfocused { get; protected set; }
public MyViewModel() {
EntryUnfocused = new Command(CompletedCommandExecutedAsync);
}
private void CompletedCommandExecutedAsync(object param)
{
System.Diagnostics.Debug.WriteLine("------------> come here....");
}
}

Related

Xamarin forms Listview selected Item fore color

bit stuck on this.
Have a list view and I want to change the theme to match the rest of my app.
Been following a few examples of how to change the selected item back color which I have working really well using custom renders, mainly this example
https://blog.wislon.io/posts/2017/04/11/xamforms-listview-selected-colour
However no example I've been able to find addresses the fore color of the selected items.
Is that something I would do with custom renders as with the background or am I backing up the wrong tree?
My list view definition is as follows
<ListView.ItemTemplate>
<DataTemplate>
<customControls:ExtendedViewCell SelectedBackgroundColor="#5DB8B3">
<ViewCell.View>
<StackLayout VerticalOptions="StartAndExpand">
<Label Text="{Binding AttributeName}"
FontSize="Small"
FontAttributes="Bold"/>
<Label Text="{Binding Description}"
FontSize="Small"/>
<Label Text="{Binding CreditorName}"
FontSize="Small"/>
</StackLayout>
</ViewCell.View>
</customControls:ExtendedViewCell>
</DataTemplate>
</ListView.ItemTemplate>
Appreciate any feedback thank
You can do this (Without a custom renderer) by adding another property to the object is bound to, and binding TextColor on the label to this new property.
Assuming your bound object looks something like this
public class BoundObject
{
public string AttributeName { get; set; }
public string Description { get; set; }
public string CreditorName { get; set; }
public int id { get; set; }
public Color TextColor { get; set; }
}
XAML
Note the ListView control added, with a name property and an ItemSelected event.
<ListView x:Name="myList" ItemSelected="myListSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout VerticalOptions="StartAndExpand">
<Label Text="{Binding AttributeName}"
FontSize="Small"
FontAttributes="Bold"
TextColor="{Binding TextColor}"
/>
<Label Text="{Binding Description}"
FontSize="Small"
TextColor="{Binding TextColor}"
/>
<Label Text="{Binding CreditorName}"
FontSize="Small"
TextColor="{Binding TextColor}"
/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code Behind
Most of the magic happens in the code behind. Note that I'm just adding a few items to the list on start here - just for debug purposes. It's important to note that the start color is also given at the time the list needs to be created.
I've also added an ID field to the BoundObject, so we can more easily identify which object we have selected.
List<BoundObject> listItems = new List<BoundObject>();
public YourPage()
{
InitializeComponent();
for (int i = 0; i < 10; i++)
{
listItems.Add(new BoundObject() { id=i, AttributeName = "Attribute " + i, Description = i + " description", CreditorName = "Creditor: " + i, TextColor = Color.Blue });
}
myList.ItemsSource = listItems;
}
private void myListSelected(object sender, SelectedItemChangedEventArgs e)
{
if (((ListView)sender).SelectedItem == null)
return;
//Get the item we have tapped on in the list. Because our ItemsSource is bound to a list of BoundObject, this is possible.
var selection = (BoundObject)e.SelectedItem;
//Loop through our List<BoundObject> - if the item is our selected item (checking on ID) - change the color. Else - set it back to blue
foreach(var item in listItems)
{
if (item.id == selection.id)
item.TextColor = Color.Red;
else
item.TextColor = Color.Blue;
}
//ItemsSource must be set to null before it is re-assigned, otherwise it will not re-generate with the updated values.
myList.ItemsSource = null;
myList.ItemsSource = listItems;
}
The key points to the code-behind are...
New property TextColor on your bound object, of type Color
Store your BoundObject in a List<BoundObject>
When populating your list for the first time, set the TextColor property in your BoundObject
In the ItemSelected event for your list, get the current selection, and update the List<BoundObject> setting the colours as your conditions need
Set the list ItemSource to null, and re-assign it to the (now updated) List<BoundObject>
Can achieve through,
a custom renderer , however with this approach the color is not applied when the cell includes a ContextAction.
Using Custom Renderer,
From bugzilla
Using Cross Platform Way (binding), this approach applying the color to all cells(layout) that including a ContextAction
Obviously in Xamarin Forms,
Possible ways to achevie
Stack Overflow discussion

How to use x:Bind with different data type than data template

I'm working on a view (called 'Familify') which shows users a list of assets, and allows them to delete an asset from the list. The assets are stored in an ObservableCollection in the ViewModel, so the command to delete simply takes the asset object and removes it from collection. I'm having issues getting the 'delete' functionality working. Here is the XAML and codebehind:
Familify.xaml
<ListView
ItemsSource="{Binding Assets}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80px" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150px" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60px" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Text="{Binding number}" FontFamily="Consolas"/>
<TextBlock
Grid.Column="1"
Text="{Binding type}"/>
<TextBlock
Grid.Column="2"
Text="add binding here"/>
<TextBlock
Grid.Column="3"
Text="add binding here"/>
<Button
Command="{x:Bind ViewModel.RemoveAssetCommand}"
CommandParameter="{Binding}"
Content=""
FontFamily="Segoe MDL2 Assets"
Grid.Column="4">
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Familify.xaml.cs
namespace asset_manager.Views
{
public sealed partial class Familify : UserControl
{
FamilifyViewModel ViewModel { get; set; }
public Familify()
{
this.InitializeComponent();
DataContextChanged += (s, e) =>
{
ViewModel = DataContext as FamilifyViewModel;
};
}
}
}
The idea is that clicking the button removes the asset from the list. (Just to note, the normal binding showing number, type, etc. is working correctly.) My thinking so far:
Try to use binding to access the RemoveAssetCommand stored in the View Model for the page. However, I couldn't get ancestral binding to work (i.e. trying to find the data context of an element higher up in the XAML hierarchy didn't work because findAncestor isn't a thing in UWP.)
x:Bind looked like a good solution, because it uses an explicit path to the property. So, if I declared ViewModel in my code behind, I could use x:Bind ViewModel.property. All well and good. I did just that, and intellisense allowed me to access the ViewModel.RemoveAssetCommand when typing it out.
However, this did not work, because I get the error no DataType defined for DataTemplate. This makes sense, so I tried two things.
x:DataType="Models:Asset" (put in the DataTemplate tag above) is the model being shown in the data template, so I tried that first. Of course, the command is not declared in the model, it's declared in the View Model, so that didn't work.
I instead tried x:DataType="ViewModels:FamilifyViewModel", thinking I could just use x:Bind with that. However, I then got an error that it couldn't cast an object of type Asset to FamilifyViewModel. This makes sense, because the object getting passed to this data template is of the type Asset.
This is a pain, because the whole reason I thought x:Bind would work is that I could just access the property directly from the ViewModel in the codebehind.
Explicitly stated, 1) is it possible to use x:Bind within a data template to access a base level property (in this case, a Prism command) on the ViewModel? and 2) is there a better way to go about implementing this functionality?
Is it possible to use x:Bind within a data template to access a base level property (in this case, a Prism command) on the ViewModel?
Yes, if you want to access a base level, you can reassign DataContext of button like following:
<Button DataContext="{Binding ElementName=Familily, Path=DataContext}"/>
The Family is the name of UserControl.
is there a better way to go about implementing this functionality?
When you put commad in the ViewModel and bind the button as above. The the bind item of button will become Family DataContext. So you could not invoke delete action directly in the ViewModel.
The best practice to implement this functionality is that put the RemoveAssetCommand in the Asset class. And use the ItemsSource of ListView as Button CommandParameter.
<Button
Command="{Binding RemoveAssetCommand}"
CommandParameter="{Binding ElementName=MyListView, Path=ItemsSource}"
Content=""
FontFamily="Segoe MDL2 Assets"
Grid.Column="4">
</Button>
Asset.cs
public class Asset
{
public string number { get; set; }
public string type { get; set; }
public ICommand RemoveAssetCommand
{
get
{
return new CommandHandler<ObservableCollection<Asset>>((item) => this.RemoveAction(item));
}
}
private void RemoveAction(ObservableCollection<Asset> items)
{
items.Remove(this);
}
}
ViewModel.cs
public class FamilifyViewModel
{
public ObservableCollection<Asset> Assets = new ObservableCollection<Asset>();
public FamilifyViewModel()
{
Assets.Add(new Asset { number = "100001", type = "hello" });
Assets.Add(new Asset { number = "100001", type = "hello" });
Assets.Add(new Asset { number = "100001", type = "hello" });
Assets.Add(new Asset { number = "100001", type = "hello" });
}
}

Windows Store App binding of CheckBox does not work (occassionally)

I have the following piece of XAML:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Background="#286C9A" Width="336" Height="22">
<TextBlock Text="{Binding Checked}" Foreground="Black" HorizontalAlignment="Left"/>
<CheckBox IsChecked="{Binding Checked}" HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
It's a template in a ListView. Checked is a bool property, and the problem is that the initial value of the property is transferred correctly to the view (both TextBlock and CheckBox). But following changes to the property is only reflected on the TextBlock, the Checkbox does not react.
Can someone tell me what happens?
EDIT:
The relevant part (I believe) of the ViewModel is this :
public class MenuGroup : ObservableCollection<MenuItem>
{
bool #checked;
public bool Checked
{
get { return #checked; }
set
{
if (#checked == value) return;
#checked = value;
OnPropertyChanged(new PropertyChangedEventArgs("Checked"));
}
}
}
EDIT: It is apparent that the binding stops working the first time I have clicked on the checkbox and thus manually changed it's state. And it works all the way if it's TwoWay binding. But why that is, I don't know.

Interaction Trigger before selectionChanged of ListPicker in Windows Phone 8

I have a issue when trigger comes in ViewModel the SelectedItem(parameter) comes the previously selected Item. I need the newly selected item as parameter on selectionChanged.
I am new in WP8. Below is the code
<toolkit:ListPicker Header="Background"
ExpansionMode="FullscreenOnly"
Template="{StaticResource ListPickerControlTemplate}"
VerticalAlignment="Top"
ItemsSource="{Binding Path=Buildings.ObjectList}"
Margin="0"
x:Name="buldings"
Padding="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=BuildingSelectionCommand}"
CommandParameter="{Binding Path=SelectedItem, ElementName=buldings}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Thanks
Vinod
Normally should you get the currently SelectionItem passed as parameter to your Command. Since the event is written in past tense, so that you should get the currently SelectedItem and not the previously one.
What you can try is to add a binding for the SelectedItem to your ListPicker and omit passing the SelectedItem to your Command as parameter.
<toolkit:ListPicker SelectedItem="{Binding SelectedBuilding, Mode=TwoWay}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=BuildingSelectionCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</toolkit:ListPicker>
Your command then needs to access the property SelectedBuilding to execute
public class BuildingSelectionCommand{
// a reference to your ViewModel that contains the SelectedBuilding-Property
public BuildingsViewModel ViewModel {
get;
set;
}
public bool CanExecute(object parameter) {
return ViewModel.SelectedBuilding != null;
}
public void Execute(object parameter){
var selectedItem = ViewModel.SelectedBuilding;
// execute command logic with selectedItem
}
}
The code can be diffrent on your side, cause it depends on how you have implemented your ViewModel and Command, but i think you should get it.
Another way without using an EventTrigger, is to execute the command directly in your SelectedBuilding-Property.
public Building SelectBuilding{
get {
return _selectedBuilding
}
set{
_selectedBuilding = value;
RaisePropertyChanged("SelectedBuilding");
if (BuildingSelectionCommand.CanExecute(_selectedBuilding)) {
BuildingSelectionCommand.Execute(_selectedBuilding);
}
}
XAML:
<i:EventTrigger EventName="SelectionChanged">
<command:EventToCommand Command="{Binding BuildingSelectionCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
Command:
RelayCommand<SelectionChangedEventArgs> BuildingSelectionCommand { get; set; }
BuildingSelectionCommand = new RelayCommand<SelectionChangedEventArgs>(async (args) => { });
You just missed the "PassEventArgsToCommand". Make sure your command has a SelectionChangedEventArgs.
The simple way to solve it is to use
SelectedItem="{Binding SelectedBuilding, Mode=TwoWay}"
as Jehof suggested and get rid of all the "<i:" trigger settings but simply handle the change in the SelectedBuilding property setter and call a method instead of using commands to wrap a method call. You are not gaining anything with a command since you are not even using CanExecute here, but simply adding more code.

Why can't get validation error display in validationSummary?

I have a form with some validations set in entity metadata class. and then binding entity instance to UI by VM. Something as below:
Xaml like:
<Grid x:Name="LayoutRoot">
<StackPanel VerticalAlignment="Top">
<input:ValidationSummary />
</StackPanel>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
<ComboBox x:Name="xTest" ItemsSource="{Binding MyList}"
SelectedItem="{Binding MyItem,Mode=TwoWay,
DisplayMemberPath="MyName"
ValidatesOnDataErrors=True,
ValidatesOnNotifyDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=True,UpdateSourceTrigger=Explicit}" />
</Grid>
Code-behind like:
public MyForm()
{
InitializeComponent();
this.xTest.BindingValidationError +=new EventHandler<ValidationErrorEventArgs>((s,e)=>{
BindingExpression be = this.xTest.GetBindingExpression(ComboBox.SelectedItemProperty);
be.UpdateSource();
if (e.Action == ValidationErrorEventAction.Added)
((ComboBox)s).Foreground = new SolidColorBrush(Colors.Red);
});
}
Metadata like:
[Required]
public string Name { get; set; }
[RequiredAttribute]
public int MyItemID { get; set; }
But when running the app, I got nothing display in valudationSummary.
For CombBox, even there is error, looks like BindingValidationError event is never fired.
How to resolve it?
Why are you using an Explicit UpdateSourceTrigger?
Silverlight validation happens inside the binding framework, when the binding is updating the source object. The way you have this, there won't be a binding validation error because you never tell the binding to update the source object. Well, actually you do, but it happens inside the validation error event handler. You've written chicken-and-egg code.
Remove your UpdateSourceTrigger on your binding or set it to Default.
Remove the explicit call to BindingExpression.UpdateSource.
Remove setting the ComboBox foreground to red - you are using NotifyOnValidationError=True, which eliminates any need to manually color the control.
Remove the DisplayMemberPath from the binding
So your XAML:
<Grid x:Name="LayoutRoot">
<StackPanel VerticalAlignment="Top">
<input:ValidationSummary />
<ComboBox x:Name="xTest" ItemsSource="{Binding MyList}"
SelectedItem="{Binding MyItem,
Mode=TwoWay,
ValidatesOnDataErrors=True,
ValidatesOnNotifyDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=True}" />
</StackPanel>
</Grid>
And your code:
public MyForm()
{
InitializeComponent();
// you don't need anything here to have the validations work
}