I have the following 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:StyleLabelInViewCell" x:Class="StyleLabelInViewCell.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:MainVM x:Key="MainVm" />
<local:IsSelectedToStyle x:Key="IsSelectedToStyle" />
<Style x:Key="SelectedStyle" TargetType="Label">
<Setter Property="FontAttributes" Value="Bold" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout BindingContext="{StaticResource MainVm}">
<ListView x:Name="ChildListView" VerticalOptions="Center" ItemsSource="{Binding Chidren}" SelectedItem="{Binding SelectedVm}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="ChildViewCell">
<ViewCell.View>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Text="<" />
<Label
Grid.Column="1"
Text="{Binding Name}"
FontAttributes="{Binding Source={x:Reference ChildListView}, Path=SelectedItem.Id, Converter={StaticResource IsSelectedToStyle}, ConverterParameter={Binding Path=Id}}"
/>
<Button Grid.Column="2" Text=">" />
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
And the following code:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using Xamarin.Forms;
namespace StyleLabelInViewCell {
public class MainVM : INotifyPropertyChanged {
public MainVM() {
Chidren = new ObservableCollection<ChildVM> {
new ChildVM(1, "Item 1"),
new ChildVM(2, "Item 2"),
new ChildVM(3, "Item 3"),
new ChildVM(4, "Item 4"),
new ChildVM(5, "Item 5"),
new ChildVM(6, "Item 6")
};
}
public ObservableCollection<ChildVM> Chidren { get; }
private ChildVM selectedVm;
public ChildVM SelectedVm {
get { return selectedVm; }
set {
if (!ReferenceEquals(selectedVm, value)) {
selectedVm = value;
OnPropertyChanged(nameof(SelectedVm));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ChildVM {
public ChildVM() { }
public ChildVM(int id, string name) {
Id = id;
Name = name;
}
public int Id { get; }
public string Name { get; }
}
public sealed class IsSelectedToStyle : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var selectedId = (int)value;
var currentItemId = (int)parameter; // This ends up being Xamarin.Forms.Binding instance
return selectedId == currentItemId ? Application.Current.MainPage.Resources["SelectedStyle"] : null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotSupportedException();
}
}
}
I'm trying to bold the label when the ItemTemplate is rending the currently selected row. The issue is that that ConverterParameter is sent in as a Xamarin.Forms.Binding instance, when I would have expected another integer.
Is there some way to get the value instead of the binding, or if not, from the Binding? Or is there another way to accomplish what I'm trying to do?
I think you can take a look to this post.
I have updated the repo. Now there is a Binding also for FontAttributes property
<ListView SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding List}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}" FontAttributes="{Binding Selected, Converter={StaticResource cnvInvertFontAttribute}}}" TextColor="{Binding Selected, Converter={StaticResource cnvInvert}}}" FontSize="18"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
when you select a row, Selected property is set in VM
public MyModel SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem != null)
_selectedItem.Selected = false;
_selectedItem = value;
if (_selectedItem != null)
_selectedItem.Selected = true;
}
}
and a IValueConverter convert the boolean "Selected" property to a FontAttribute
public class SelectedToFontAttributeConverter : IValueConverter
{
#region IValueConverter implementation
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
if ((Boolean)value)
return FontAttributes.Bold;
else
return FontAttributes.None;
}
return FontAttributes.None;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Related
I would really like to know how I can bind an ImageSource to a specific element's property in an ObservableCollection... Right now I have this:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyNameSpace"
x:Class="MyNameSpace.MainPage"
x:Name = "Main"
Padding="5,20,5,5"
BindingContext="{x:Reference Name=Main }">
<Grid
<ImageButton
Grid.Column="0"
Grid.Row="0"
Source="{Binding _hand[0].ResourceId , Converter={StaticResource
StringToSourceConverter}}"
>
</ImageButton>
<ImageButton
Grid.Column="1"
Grid.Row="0"
Source="{Binding _hand[1].ResourceId , Converter={StaticResource
StringToSourceConverter}}"
>
</ImageButton>
<ImageButton
Grid.Column="0"
Grid.Row="1"
Source="{Binding _hand[2].ResourceId , Converter={StaticResource
StringToSourceConverter}}"
>
</ImageButton>
<ImageButton
Grid.Column="1"
Grid.Row="1"
Source="{Binding _hand[3].ResourceId , Converter={StaticResource
StringToSourceConverter}}"
>
</ImageButton>
</Grid>
</ContentPage>
I would like to bind the ImageSource to the following ObservableCollection of Cards...
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
private ObservableCollection<Card> _hand;
public MainPage()
{
Init();
InitializeComponent();
}
private void Init()
{
_hand = new ObservableCollection<Card>()
{
new Card("image1.jpg"),
new Card("image2.jpg"),
new Card("image3.jpg"),
new Card("image4.jpg")
};
}
}
My Card class looks something like this:
public Card ( string resourceId)
{
ResourceId = resourceId;
}
public string ResourceId { get; set; }
The Converter used :
public class ToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string ResourceId = value.ToString();
if (String.IsNullOrWhiteSpace(ResourceId))
return null;
return ImageSource.FromResource(ResourceId);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
My question now is how do I make this code work? Also I have methods that switch elements in the collection. Where do I implement PropertyChangedEvent? Thank you guys a lot :)
I have a ListView in XAML and a List<string> that holds local embedded image paths. I am not able to show images in List. By the way I am able to show as a single image by
<Image Source="{local:ImageResource TypingApplication.Images.Icons.Search.png}" />
But I cannot show the images in ListView. Here is my XAML code
<ListView x:Name="ListView"
ItemsSource="{Binding ListItems}"
IsEnabled="True"
IsVisible="True"
RowHeight="40"
Opacity="0.9">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Image Source="{local:ImageResource TypingApplication.Images.Icons.{Binding .}}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have added ImageResourceExtension in Extensions folder and xmlns:local="clr-namespace:TypingApplication.Extensions" in XAML, as I mentioned I can show Single Image, only there is problem with ListView.
Here is my C# code that contains List and Constructor
public List<string> ListItems
{
get
{
return new List<string>()
{
"Home.png",
"Favorite.png",
"Search.png"
};
}
}
public HomePage()
{
InitializeComponent();
this.BindingContext = this;
}
Please note that I am using shared Images in my project. I have set Properties of all Images to Embedded resource in SolutionExplorer.
Change list to ObservableCollection
IValueConverter implementation to convert your binding to desired value
The image property should be set to EmbeddedResource
public class EmbeddedToImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string fileName && parameter is String assemblyName)
{
try
{
var imageSource = ImageSource.FromResource(assemblyName + "." + fileName, typeof(EmbeddedToImageSourceConverter).GetTypeInfo().Assembly);
return imageSource;
}
catch (Exception)
{
return value;
}
}
else
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
XAML
<ContentPage.Resources>
<local:EmbeddedToImageSourceConverter x:Key="converter" />
</ContentPage.Resources>
In the listview add binding w.r.to converter resource we just created.
<Image Source="{Binding ., Converter={StaticResource converter}, ConverterParameter='TypingApplication.Images.Icons'}" />
If you are not using View Model (MVVM), you can directly specify image file's name in XAML as:
<Image Source="{Binding Source='ImageFileName.png', Converter={StaticResource converter}, ConverterParameter='TypingApplication.Images.Icons'}" />
If you want to add Embedded image in listview, according to json's reply, your binding have some problem, you can use IValueConverter to convert image path as correct.
I do one sample according to your code, you can take a look:
<ListView HasUnevenRows="True" ItemsSource="{Binding ListItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Image
HeightRequest="100"
Source="{Binding ., Converter={StaticResource imageconverter}}"
WidthRequest="100" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentPage.Resources>
<local:imageconverter x:Key="imageconverter" />
</ContentPage.Resources>
The Image converter:
public class imageconverter : IValueConverter
{
public string Source { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Source = (string)value;
if (Source == null)
return null;
// Do your translation lookup here, using whatever method you require
var imageSource = ImageSource.FromResource("demo3."+Source, typeof(ImageResourceExtension).GetTypeInfo().Assembly);
return imageSource;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can change demo3 as TypingApplication according to your code.
public partial class Page14 : ContentPage
{
public ObservableCollection<string> ListItems { get; set; }
public Page14()
{
InitializeComponent();
ListItems = new ObservableCollection<string>()
{
"image1.jpg","image2.png","image3.png"
};
this.BindingContext = this;
}
}
As Prateek's reply, I suggest you can change List<> to Observablecollection<>, because it implement INotifyPropertyChanged interface, notify data changed.
https://learn.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.observablecollection-1?view=netframework-4.8
I have a ListView that binds its items from an ObservableCollection, and a Button that changes an "Amount" property of a specific object of that ObservableCollection. And I want to change the BackgroundColor of these Items whose "Amount" has already been changed.
I've searched for a solution for that, but I couldn't find any.
Does anybody know a way for solving that?
One way to do it would be to add a new property, something like HasAmountChanged, bind the background color of the viewcell to that property, and use a ValueConverter to set the color. This would look something like the following:
The object class with the properties:
public class MyObject : INotifyPropertyChanged
{
double amount;
bool hasAmountChanged = false;
public event PropertyChangedEventHandler PropertyChanged;
public MyObject(double amount)
{
this.amount = amount;
}
public double Amount
{
get => amount;
set
{
if (amount != value)
{
amount = value;
OnPropertyChanged(nameof(Amount));
HasAmountChanged = true;
}
}
}
public bool HasAmountChanged
{
get => hasAmountChanged;
set
{
if (hasAmountChanged != value)
{
hasAmountChanged = value;
OnPropertyChanged(nameof(HasAmountChanged));
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The view. Notice the stacklayout inside the ViewCell, that's where the background color is set:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Delete"
x:Class="Delete.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:ListViewBackgroundColorConverter x:Key="ListViewColorConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<Button Text="Click Me" Clicked="ButtonClicked" />
<ListView ItemsSource="{Binding MyItemsSource}" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Spacing="15"
BackgroundColor="{Binding HasAmountChanged, Converter={StaticResource ListViewColorConverter}}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<Label Text="FOO 1"/>
<Label Text="{Binding Amount}"/>
<Label Text="{Binding HasAmountChanged}" />
<Label Text="FOO 4"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
The code behind of the view, included for completeness:
public partial class MainPage : ContentPage
{
public ObservableCollection<MyObject> MyItemsSource { get; set; }
public MainPage()
{
InitializeComponent();
MyItemsSource = new ObservableCollection<MyObject>
{
new MyObject(1.14),
new MyObject(1.14),
new MyObject(1.14),
new MyObject(1.14),
new MyObject(1.14),
};
BindingContext = this;
}
void ButtonClicked(object sender, EventArgs e)
{
var rnd = new Random();
var myObject = MyItemsSource[rnd.Next(0, MyItemsSource.Count)];
myObject.Amount = 5.09;
}
}
And finally the most important part, the converter:
public class ListViewBackgroundColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Color.LawnGreen : Color.DarkRed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Note that you would actually want to check it's a bool coming in and handle that as well.
You could implement an array of booleans and change them to true when Amount gets changed. Then you might want to create a custom renderer for the color of each ListView.
My Model is
class MainPageList
{
public string item { get; set; }
public List<string> sitem { get; set; }
public List<string> ssitem { get; set; }
}
and My View Model is
class MainPageViewModel
{
public List<MainPageList> MList {
get{
return new List<MainPageList>
{
new MainPageList(){item="Item1",sitem= new List<string>{"sub item1","sub item2","sub item3"}, ssitem= new List<string>{"sub sub item1","sub sub item2","sub sub item3"}}
};
}
}
public string item { get; set; }
public List<string> sitem { get; set; }
public List<string> ssitem { get; set; }
}
and XAML is
<StackLayout>
<Label Text="Introcduction Page" Font="18" />
<ListView x:Name="MainPageList" ItemsSource="{Binding MList} " >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding item} "></Label>
<StackLayout Padding="10,10,0,0">
<Label Text="{Binding sitem} "></Label>
<StackLayout Padding="10,10,0,0">
<Label Text="{Binding ssitem} "></Label>
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
I can get the text for sitem but sitem and ssitem are not being displayed in Emulator or Device.
Any help appreciated in advance, please.
You can't bind a List to a StackLayout and have it automatically "inflate" the data. You can either use a ListView (which will apply a template to each item in the List) or manually build the collection of child objects that you want to display in your StackLayout.
You can't bind a list into a Label. It should be a primitive type or a Class which implements ToString() method. If you want to show all items into a Label you can write a Converter like:
public class ListToString : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(value is IEnumerable)
{
string retval ="";
for(string item in (IEnumerable)value) //use your string format here
retval+= item+",";
return retval;
}
else
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and use it on WPF side:
<!--define static resource -->
<ContentPage.Resources>
<ResourceDictionary>
<Converters:ListToString x:Key="ListToString"/>
</ResourceDictionary>
</ContentPage.Resources>
<!--use it -->
<Label Text="{Binding sitem,Converter={StaticResource ListToString}} "></Label>
I have combobox with binded dictionary
Dictionary:
public Dictionary<string,DateTime> TimeItems { get; set; }
<ComboBox Grid.Column="3"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
ItemsSource="{Binding TimeItems}"
SelectedIndex="0"
SelectedItem="{Binding SelectedItem}">
How can i bind to public DateTime SelectedItem { get; set; } value of TimeItems
You can use converter to bind the the value of Dictionary to SelectedItem.
public class DictionaryValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((KeyValuePair<string, DateTime>)value).Value;
}
}
XAML
<ComboBox Grid.Column="3" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
ItemsSource="{Binding TimeItems}"
SelectedItem="{Binding SelectedItem, Converter={StaticResource DictionaryValueConverter}}" />
You can set SelectedValuePath to "Value" (since each item is a KeyValuePair) and then bind SelectedValue property to your property:
<ComboBox Grid.Column="3"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
ItemsSource="{Binding TimeItems}"
SelectedIndex="0"
SelectedValuePath="Value"
SelectedValue="{Binding SelectedItem}"/>
If you are only interested in displaying and retrieving the values of the Dcitionary then you can use the below option
ViewModel
class ExpenseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
var pc = PropertyChanged;
if (pc != null)
{
pc(this, new PropertyChangedEventArgs(propName));
}
}
private void Populate()
{
_mySource.Add("Test 1", DateTime.Now);
_mySource.Add("Test 2", DateTime.Now.AddDays(1));
_mySource.Add("Test 3", DateTime.Now.AddDays(2));
_mySource.Add("Test 4", DateTime.Now.AddDays(3));
NotifyPropertyChanged("MySource");
}
private Dictionary<string, DateTime> _mySource;
public Dictionary<string, DateTime> MySource
{
get { return _mySource; }
}
public ExpenseViewModel()
{
_mySource = new Dictionary<string, DateTime>();
Populate();
}
public DateTime SelectedDate
{
get;
set;
}
}
View.xaml
<StackPanel HorizontalAlignment="Left">
<StackPanel.Resources>
<local:Converters x:Key="Converter"/>
</StackPanel.Resources>
<ComboBox x:Name="cmbBox" Width="200" Margin="10,0,20,0" ItemsSource="{Binding MySource, Converter={StaticResource Converter}}"
SelectedItem="{Binding SelectedDate, Mode=TwoWay}" Height="20">
</ComboBox>
</StackPanel>
where local is the xmlns of the project containing the converter
Converter
public class Converters : IValueConverter
{
private static readonly PropertyInfo InheritanceContextProp = typeof(DependencyObject).GetProperty("InheritanceContext", BindingFlags.NonPublic | BindingFlags.Instance);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var dic = value as Dictionary<string, DateTime>;
return dic.Values;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In view behind code I have just defined the datacontext
Try this:
foreach (KeyValuePair<string,DateTime> values in TimeItems){
comboBox.Items.Add(values.Value);
}