I use Xamarin.Forms to define a ListView. This ListView defines some ContextActions on inside the ViewCell. Depending on the platform, these context actions are then presented to the user. In Android, this is triggered by long-pressing the specific item. Sadly, this Item will not be (properly) highlighted, as can be seen in this screenshot (I long-pressed Third Item, sadly I can't yet embed images).
Is there a way to modify the Cell when the context menu opens? Specifically asking for a solution for Android, but a general answer is welcome as well. The goal eventually is to improve highlighting, e.g. by changing the cell's background color. Modifying the cell, when one ContextAction is pressed, is not what I am looking for.
I browsed through the source code of Xamarin.Forms and thought about somehow inheriting from e.g. the ViewCell class, but couldn't find an event or command that would be triggered / called upon long-pressing an item. I have set up a simple repository to which illustrates the behavior: GitHub repository
The most important code snippets
ListView definition in XAML
<?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:local="clr-namespace:ListViewContextMenu" x:Class="ListViewContextMenu.ListViewContextMenuPage">
<ListView x:Name="MyListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Action" Command="{Binding OnAction}" CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>
<Label Text="{Binding Name}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
MyItem definition (MVVM)
using System.Diagnostics;
using Xamarin.Forms;
namespace ListViewContextMenu
{
public class MyItem
{
public string Name { get; set; }
public Command OnAction { get; set; }
public MyItem()
{
OnAction = new Command((obj) => Debug.WriteLine($"Item {obj.ToString()} clicked"));
}
}
}
No need for custom renderer - you can simply add following tag(s) to styles.xml (location: Android project > Resources > values > styles.xml)
<style name="MyTheme" parent="MyTheme.Base">
<item name="android:colorLongPressedHighlight">#color/ListViewHighlighted</item>
</style>
<color name="ListViewHighlighted">#A8A8A8</color>
More details can be found at this post.
Related
In .xaml file I am trying to bind to a listed custom class as ObeservableCollection object.
I can successfully update my variables and get the ObservableCollection updated. I can check it rendering it as:
<ListView ItemSource="{Binding myCustomObservableCollection}"/>
However, even if I can determine the number of the entries in the list, I cannot access the properties of my custom class.
I tried with this, with no success as list's rows are empty. Even using Text="{Binding Id}" doesn't work since it tells me that "Id" is not a property inside myCustomViewModel:
<ListView
x:DataType="vm:CustomtViewModel"
BackgroundColor="LightSlateGray"
HasUnevenRows="True"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding myCustomObservableCollection}"
SeparatorColor="Black">
<ListView.ItemTemplate>
<DataTemplate>
<label Text="{Binding Source={StaticSource myCustomClass}", Path=Id}/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Of course I have inserted my custom class into the .xaml with:
<ContentPage.Resources>
<local:myCustomClass x:Key="myCustomClass" />
</ContentPage.Resources>
And Id is one of the properties I need into the public class in my Models
namespace myApp.Models {
public class myCustomClass : INotifyPropertyChanged
{
private string _id;
public event PropertyChangedEventHandler PropertyChanged;
public string Id
{
get => _id;
set {
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));
}
}
}
}
So I wonder how to effectively read every entry of the list as an object which I could parse the properties in it.
Thanks so much
Did you check the official document about Binding Cells in the ListView? The myCustomClass didn't have to inherit from the INotifyPropertyChanged interface.
Just make sure there is public ObservableCollection<myCustomClass> { get; set; } in your viewmodel. Such as:
public class CustomtViewModel
{
public ObservableCollection<myCustomClass> myCustomObservableCollection { get; set; }
public CustomtViewModel()
{
// you can initialize the myCustomObservableCollection's data in the construction method.
}
}
In adddition, I see you used the x:DataType="vm:CustomtViewModel" for the listview. The official document said:
Set an x:DataType attribute on a VisualElement to the type of the object that the VisualElement and its children will bind to.
So you can just binding the Id like Jason said:
<ListView.ItemTemplate>
<DataTemplate>
<Label Text={Binding Id}/>
</DataTemplate>
</ListView.ItemTemplate>
In addition, you can refer to the official sample about listview mvvm binding on the github.This is the viewmodel's code and the page's code.
Also thanks to Liyun Zhang and ToolmakerSteve I came up with a solution.
Indeed it's important to set the correct x:DataType and I found out it can be done even multiple times pointing at different classes, linking different types of data
Here's my ListView in xaml now:
<ListView
x:Name="customListName"
x:DataType="vm:CustomViewModel"
ItemsSource="{Binding myCustomObservableCollection}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:myCustomClass"> <!--THIS SAVED THE DAY-->
<ViewCell>
<Label Text="{Binding Id}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Now the object extracted from list is correctly read referencing to its own class.
The trick is about adding x:DataType="local:myCustomClass" to the DataTemplate tag after I added a reference in the xaml like this:
<ContentPage.Resources>
<local:myCustomClass x:Key="myCustomClass" />
</ContentPage.Resources>
(I insert this also here for ease of reading if someone else met the same issue)
It worked like a charm!
Hope this can save someone else from headache! Cheers.
On start of application the ComboBox with a valid Text Property incorrectly shows a placeholder text rather than the bound text value - until the user clicks the ComboBox.
The click causes the correct bound Text to show and stay shown. The related TextBox linked to same property correctly shows the bound value at all times.
If a Combo List item is selected it works as expected indicating Binding is correct.
It looks like the default template: (generic.xaml from C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.18362.0\Generic.xaml)
is likely incorrect as the Text should show when a value exists, rather than the Placeholder text. If this is the cause, then I do not yet have the skill to redefine the visual states correctly and would benefit from a hint.
Can anyone point me in the right direction so I can show the Text property at initial start without a click required.
The user control code used is:
Demonstrated with a WinUI3 usercontrol containing TextBox and a ComboBox - Text of each bound to the same property in two way mode so each will update with property changed.
Using current nuget package of CommunityToolkit 7.1.2 which uses
.net5.0-windows10.0.18362
Xaml Code ComboBoxTest.xaml:
<UserControl
x:Class="mvvmTry1.Views.ComboBoxTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:mvvmTry1.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal" Spacing="20" Background="AntiqueWhite">
<TextBox Header="Current Value" Text="{x:Bind MyText,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinWidth="60"
Height="Auto" />
<ComboBox
Header=" ComboBox"
IsEditable="True"
ItemsSource="{x:Bind MyListItems, Mode=TwoWay}"
MinWidth="100"
PlaceholderText="PlaceHolder Text"
PlaceholderForeground="Green"
Text="{x:Bind MyText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
</StackPanel>
</UserControl>
CodeBehind ComboBoxTest.xaml.cs:
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml.Controls;
using System.Collections.ObjectModel;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace mvvmTry1.Views
{
[ObservableObject]
public partial class ComboBoxTest : UserControl
{
[ObservableProperty]
private string myText ="MyText value";
public ObservableCollection<string> MyListItems = new();
public ComboBoxTest()
{
this.InitializeComponent();
MyListItems.Add("Item 1");
MyListItems.Add("Item 2");
MyListItems.Add("Item 3");
}
}
}
I'm working on a Xamarin App where I want to dynamically display a list of Registry Numbers from a Class Registry.
After the list of numbers is displayed, the user should choose one of them to login in the App.
I have decided that this list will be displayed as a list of Buttons, because once the user clicks it, nothing else needs to be done. However, most of documentation regarding Binding and ListView does not use Buttons as displaying element.
I have decided to follow the steps on this excellent video but I keep receiving the following error:
Binding: 'LocalCommand' property not found on '31348', target property: 'Xamarin.Forms.Button.Command'
Binding: 'LocalCommand' property not found on '10227', target property: 'Xamarin.Forms.Button.Command'
Actually, 31348 and 10227 are the numbers that I want to display. And indeed I indicated them as the Binding context at some point. But I would like to "change" that Binding so I can invoke the LocalCommand method. Probably implementing the LocalCommand in the object would solve the issue, but I definitely don't want to do that!
Questions:
Is there a better and simpler way to do this?
How can I do to "bring back" the Binding to the LocalCommand?
I'm still learning about Binding, so any tips would be really useful!
Thanks!
RegistryPage.xaml
<?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:behavior1="clr-namespace:SPConsigMobile.Utils"
x:Class="App.Views.RegistryPage"
BackgroundColor="{StaticResource AppBackgroundColor}"
Title="App"
NavigationPage.HasNavigationBar="false">
<ContentPage.Content>
<ListView x:Name="RegistryView"
ItemsSource="{Binding User.Registry}"
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Button Text="{Binding Number}"
Command="{Binding LocalCommand}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
RegistryPage.xaml.cs
namespace App.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RegistryPage : ContentPage
{
public RegistryPage()
{
InitializeComponent();
RegistryView.ItemsSource = User.Registries;
}
public ICommand LocalCommand => new Command(CommandClicked);
private void CommandClicked()
{
Console.WriteLine("Command Button Clicked!");
}
}
}
In general when you set the ItemSource property of a control (in this case of your ListView), you have also to set the DataTemplate.
Now, the BindingContext of view inside the DataTemplate is an item of the collection you have binded to. In your case, because you have set the ItemSource to be a Collection of PhoneNumber, the bindingContext of each view is a PhoneNumber.
So when you are trying to acess your command with 'Command="{Binding LocalCommand}"', what you are doing is to search a LocalCommand Property inside a PhoneNumber class. What you need instead is to search it inside your Page class. So, give a name to your ContentPage with x:Name, then reference the source to your command binding to be the Root Page, and the Path to be the path to the command, starting from the Source (so NumberSelectedCommand in my example). the command parameter should be instead exactly the number, so it's an empty path Binding.
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App1.RegistryPage"
x:Name="Root"
>
<StackLayout>
<ListView x:Name="RegistryView"
ItemsSource="{Binding User.Registry}"
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Button Text="{Binding Number}"
Command="{Binding Source={x:Reference Root}, Path=NumberSelectedCommand}"
CommandParameter="{Binding}"
/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
RegistryPage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RegistryPage : ContentPage
{
public RegistryPage()
{
InitializeComponent();
RegistryView.ItemsSource = User.Registries;
NumberSelectedCommand = new Command<PhoneNumber>(OnNumberSelected);
}
// Commands
public ICommand NumberSelectedCommand { get; }
// Commands Handlers
private void OnNumberSelected(PhoneNumber selectedNumber)
{
// Do what you need with selected number.
}
}
I use Windows Ribbon for WPF with Caliburn Micro. Is it possible to move RibbonTabs into different views with ViewModels so views can be more readable?
I have MainToolbarViewModel class bound with a MainToolbarView.
MainToolbarViewModel:
public TabViewModel Tab { get; set; }
public MainToolbarViewModel()
{
this.Tab = new TabViewModel();
}
MainToolbarView:
<ribbon:Ribbon HorizontalAlignment="Stretch" VerticalAlignment="Top" SelectedIndex="0">
<Ribbon.HelpPaneContent>
<RibbonButton SmallImageSource="../Resources/WindowsIcons/help.png"></RibbonButton>
</Ribbon.HelpPaneContent>
<ContentControl x:Name="Tab"></ContentControl>
</ribbon:Ribbon>
I want the ContentControl to work as a RibbonTab. This approach works well with standard controls, but with ribbon it just simply doesn't show anything.
TabViewModel:
public class TabViewModel : Screen
{
public TabViewModel()
{
DisplayName = "New Tab";
}
}
TabView:
<UserControl ......>
<RibbonTab IsSelected="True" Header="{Binding DisplayName}">
<RibbonGroup Header="Types">
<RibbonButton Content="Test"></RibbonButton>
</RibbonGroup>
</RibbonTab>
</UserControl>
I read about making ViewModels for RibbonTab, RibbonGroup, RibbonButton but this just seems crazy, because I would need to create ViewModel for every control.
Additionally most of the answers I read were at least 1 year old. Anything changed since? What is the easiest way to move RibbonTabs to a different viewmodel?
UPDATE:
<ContentControl x:Name="Tab"></ContentControl>
Renders RibbonTab's content inside its header, so basically I have RibbonGroup
inside RibbonTab's header.
I think it's because Ribbon treats <ContentControl> as a header of some sort rather than RibbonTab.
My problem is similar to the one described in this question:
WPF MVVM Button Control Binding in DataTemplate
Here is my XAML:
<Window x:Class="MissileSharp.Launcher.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MissileSharp Launcher" Height="350" Width="525">
<Grid>
<!-- when I put the button here (outside the list), the binding works -->
<!--<Button Content="test" Command="{Binding Path=FireCommand}" />-->
<ListBox ItemsSource="{Binding CommandSets}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- I need the button here (inside the list), and here the binding does NOT work -->
<Button Content="{Binding}" Command="{Binding Path=FireCommand}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
It's just a ListBox, bound to an ObservableCollection<string> named CommandSets (which is in the ViewModel).
This binding works (it displays a button for each item in the collection).
Now I want to bind the button to a command (FireCommand), which is also in the ViewModel.
Here's the relevant part of the ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public ICommand FireCommand { get; set; }
public ObservableCollection<string> CommandSets { get; set; }
public MainWindowViewModel()
{
this.FireCommand = new RelayCommand(new Action<object>(this.FireMissile));
}
private void FireMissile(Object obj)
{
System.Windows.MessageBox.Show("fire");
}
}
The binding of this button does NOT work.
From what I've understood from the question I linked above, the binding doesn't work because:
(correct me if I'm wrong)
The button is inside the ListBox, so it only "knows" the binding of the ListBox (the ObservableCollection, in this case), but not the binding of the main window
I'm trying to bind to a command in the main ViewModel of the main window (which the button doesn't "know")
The command itself is definitely correct, because when I put the button outside the ListBox (see the XAML above for an example), the binding works and the command is executed.
Apparently, I "just" need to tell the button to bind to the main ViewModel of the form.
But I'm not able to figure out the right XAML syntax.
I tried several approaches that I found after some googling, but none of them worked for me:
<Button Content="{Binding}" Command="{Binding RelativeSource={RelativeSource Window}, Path=DataContext.FireCommand}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, Source={StaticResource MainWindow}}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
Could someone please:
give me the proper XAML to bind the button inside the ListBox to a command in the form's MainViewModel?
point me to a link where this advanced binding stuff is explained in a way that a WPF/MVVM beginner can understand?
I'm feeling like I'm just copying and pasting arcane XAML incantations, and so far I don't have any clue (and can't find any good documentation) how I would figure out by myself in which cases I'd need RelativeSource or StaticResource or whatever instead of a "normal" binding.
It's:
{Binding DataContext.FireCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}
No need to walk up to the root unless you actually change the DataContext along the way, but as the ListBox seems to bind to a property on the main VM this should be enough.
The only thing i recommend reading is the Data Binding Overview, and the Binding class documentation (including its properties).
Also here is a short explanation on how bindings are constructed: A binding consists of a source and a Path relative to that source, by default the source is the current DataContext. Sources that can be set explicitly are: Source, ElementName & RelativeSource. Setting any of those will override the DataContext as source.
So if you use a source like RelativeSource and want to access something in the DataContext on that level the DataContext needs to appear in the Path.
This may be considered unrelated by most, but this search is only 1 of 3 results that you'll find searching for data binding commands to controls inside a data template--as it relates to Xamarin Forms. So, maybe it'll help someone now-a-days.
Like me you may wonder how to bind commands inside a BindableLayout. Credit jesulink2514 for answering this at Xamarin Forums, where it's probably overlooked by many because of all the comments. Here's his solution, but I'm including the link below:
<ContenPage x:Name="MainPage">
<ListView Grid.Row="1"
ItemsSource="{Binding Customers}"
VerticalOptions="Fill"
x:Name="ListviewCustomer">
<ListView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Property}"/>
<Button Command="{Binding BindingContext.ItemCommand, Source={x:Reference MainPage}}"
CommandParameter="{Binding .}">Click me</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
https://forums.xamarin.com/discussion/comment/217355/#Comment_217355