iam trying to create xmarin forms application witch use a collection view
that contain two item ( label = binding name & switch = binding switched )
but i have a problem with the switch its not update without Scrolling the collection
i use Model-View-ViewModel to bind my data and do operation
that`s my xaml :
<CollectionView x:Name="GroupsCV"
ItemsSource="{Binding Groups}"
SelectionMode="Multiple"
EmptyView="No Data"
SelectionChangedCommand="{Binding SelectedCommand}"
SelectionChangedCommandParameter="{Binding Source={x:Reference GroupsCV}}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="15">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="0.1*"/>
</Grid.ColumnDefinitions>
<Switch Style="{StaticResource SwitchStyle}" IsEnabled="False" IsToggled="{Binding Switched}" Grid.Column="0"/>
<Label Text="{Binding Name}" Grid.Column="1" Style="{StaticResource LabelStyle}"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
and this my code from View Model:
public GroupsViewModel()
{
datarepo.conn.CreateTable<Group>();
_groups = datarepo.conn.Table<Group>().ToList();
//GroupsCount = Groups.Count();
}
private IEnumerable<Group> _groups;
public IEnumerable<Group> Groups
{
get => _groups;
set
{
if (_groups != value)
{
_groups = value;
OnPropertyChanged(nameof(Groups));
}
}
}
public ICommand SelectedCommand
{
get
{
return new Command<CollectionView>((s) =>
{
var x = s.SelectedItems.Cast<Group>();
foreach (var e in x)
{
foreach (var z in Groups)
{
if (z.Link == x..Link)
{
if (z.Switched == false)
{
z.Switched = true;
}
else
{
z.Switched = false;
}
break;
}
}
}
OnPropertyChanged(nameof(Groups));
});
}
}
All Thing Work fine only the ui doesn`t update !
Update With Group Code
[Table("Group")]
public class Group
{
[PrimaryKey, AutoIncrement()]
public int? Id { get; set; }
[Unique]
public string Link { get; set; }
public string Name { get; set; }
public string MemberCount { get; set; } = null;
public bool Switched { get; set; }
}
Can you change your Group class like this?
[Table("Group")]
public class Group: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
[PrimaryKey, AutoIncrement()]
public int? Id { get; set; }
[Unique]
public string Link { get; set; }
public string Name { get; set; }
public string MemberCount { get; set; } = null;
public bool Switched { get; set; }
}
Related
I'm trying to bind ListView using ItemsSource="{Binding modelname}" it is working fine with one model(Ads), when I added two Models (Ads) and (AdsImg) it's return nothing :
public class Ads
{
public string Titel { get; set; }
public string Description { get; set; }
public List<AdsImg> AdsImg { get; set; }
}
public class AdsImg
{
public int Id { get; set; }
public string ImgPath { get; set; }
public int AdId { get; set; }
}
I combine them in ViewModels (AdsViewModel)
public class AdsViewModel
{
public AdsImg AdsImg { get; set; }
public Ads Ads { get; set; }
}
Xaml page :
<ListView x:Name="AdsListView" ItemsSource="{Binding AdsViewModel}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Image Source="{Binding AdsImg.ImgPath}" />
<Label Text="{Binding Ads.Titel}" TextColor="Black"></Label>
<Label Text="{Binding Ads.Description}" TextColor="Black"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Xaml.cs
public AdsPage()
{
InitializeComponent();
GetAds();
}
private async void GetAds()
{
HttpClient client = new HttpClient();
var response = await client.GetStringAsync("https://example.net/api/Ad");
var Ads = JsonConvert.DeserializeObject<List<AdsViewModel>>(response);
AdsListView.ItemsSource = Ads;
}
Update I replaced this code:
AdsImg img = new AdsImg() {ImgPath = "imagePath"};
Ads ads = new Ads() { Titel = "title",Description = "des" };
with this:
HttpClient client = new HttpClient();
var response = await client.GetStringAsync("https://example.com/api/Ad");
var Ads = JsonConvert.DeserializeObject<List<AdsViewModel>>(response);
but it is not working
I would give you some suggestions:
Don't name the property's name the same as the class name, for example: public AdsImg AdsImg, it looks confusing when you use AdsImg.
If you want to bind the ItemSource in xaml, you should bind to a List<AdsViewModel> instead of a single ViewModel.
I wrote a example and hope you can get some idea from it:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
AdsViewModel vm = new AdsViewModel();
vm.getData();
BindingContext = vm;
}
}
public class AdsViewModel : INotifyPropertyChanged
{
private List<AdsViewModel> _modelDatas;
public List<AdsViewModel> modelDatas
{
get { return _modelDatas; }
set
{
_modelDatas = value;
OnPropertyChanged("modelDatas");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
public AdsImg AdsImgModel { get; set; }
public Ads AdsModel { get; set; }
public AdsViewModel()
{
}
public void getData() {
AdsImg img = new AdsImg() {ImgPath = "imagePath"};
Ads ads = new Ads() { Titel = "title",Description = "des" };
modelDatas = new List<AdsViewModel>();
modelDatas.Add( new AdsViewModel() { AdsImgModel = img, AdsModel = ads });
modelDatas.Add(new AdsViewModel() { AdsImgModel = img, AdsModel = ads });
modelDatas.Add(new AdsViewModel() { AdsImgModel = img, AdsModel = ads });
}
}
public class Ads
{
public string Titel { get; set; }
public string Description { get; set; }
public List<AdsImg> AdsImg { get; set; }
}
public class AdsImg
{
public int Id { get; set; }
public string ImgPath { get; set; }
public int AdId { get; set; }
}
And in Xaml:
<ListView x:Name="AdsListView" ItemsSource="{Binding modelDatas}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Image Source="{Binding AdsImg.ImgPath}" />
<Label Text="{Binding AdsModel.Titel}" TextColor="Black"></Label>
<Label Text="{Binding AdsModel.Description}" TextColor="Black"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Sample project has been uploaded here
and please feel free to ask me any question!
I have a problem. I have the following List:
Set AlbumsForList = new Set
{
Name = album.Name,
Pictures = new SetPictures
{
Picture = new List<SetPicture>()
},
Price = album.Price
};
albumList.Add(AlbumsForList);
And the SetPicture has a element called imageSource, that I want to bind to an image.
Here is the XAML:
<CollectionView ItemsSource="{Binding albumList}" SelectionMode="Single"
SelectionChanged="OnCollectionViewSelectionChanged">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid HeightRequest="100" VerticalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="40"/>
<RowDefinition Height="10"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3"/>
</Grid.ColumnDefinitions>
<ff:CachedImage Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Aspect="AspectFill" Source="{Binding DemoImage}" />
<Label Grid.Row="0" Grid.Column="3" VerticalTextAlignment="Center" VerticalOptions="Center"
HorizontalOptions="StartAndExpand" Text="{Binding Name}" TextColor="Black" FontAttributes="Bold"
FontSize="18" />
<ScrollView Orientation="Horizontal" Grid.Row="1" Grid.Column="3" Grid.ColumnSpan="2">
<StackLayout BindableLayout.ItemsSource="{Binding Pictures}" Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<ff:CachedImage HeightRequest="40" WidthRequest="55" Aspect="AspectFill" Source="{Binding imageSource}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
And finally here are my classes:
[XmlRoot(ElementName = "picture")]
public class SinglePicture
{
[XmlElement(ElementName = "id")]
public string Id { get; set; }
[XmlElement(ElementName = "name")]
public string Name { get; set; }
[XmlElement(ElementName = "sizes")]
public Sizes Sizes { get; set; }
[XmlIgnore]
public ImageSource imageSource { get; set; }
}
[XmlRoot(ElementName = "pictures")]
public class SinglePictures
{
[XmlElement(ElementName = "picture")]
public List<SinglePicture> Picture { get; set; }
}
[XmlRoot(ElementName = "picture")]
public class SetPicture
{
[XmlElement(ElementName = "id")]
public string Id { get; set; }
[XmlElement(ElementName = "name")]
public string Name { get; set; }
[XmlElement(ElementName = "size")]
public string Size { get; set; }
[XmlElement(ElementName = "price")]
public string Price { get; set; }
[XmlElement(ElementName = "quantity")]
public string Quantity { get; set; }
[XmlElement(ElementName = "sizes")]
public Sizes Sizes { get; set; }
[XmlIgnore]
public ImageSource imageSource { get; set; }
}
[XmlRoot(ElementName = "pictures")]
public class SetPictures
{
[XmlElement(ElementName = "picture")]
public List<SetPicture> Picture { get; set; }
}
[XmlRoot(ElementName = "set")]
public class Set
{
[XmlElement(ElementName = "name")]
public string Name { get; set; }
[XmlElement(ElementName = "price")]
public string Price { get; set; }
[XmlElement(ElementName = "pictures")]
public SetPictures Pictures { get; set; }
[XmlIgnore]
public ImageSource DemoImage { get; set; }
}
[XmlRoot(ElementName = "sets")]
public class Sets
{
[XmlElement(ElementName = "set")]
public List<Set> Set { get; set; }
}
[XmlRoot(ElementName = "size")]
public class Size
{
[XmlElement(ElementName = "id")]
public string Id { get; set; }
[XmlElement(ElementName = "name")]
public string Name { get; set; }
[XmlElement(ElementName = "price")]
public string Price { get; set; }
}
[XmlRoot(ElementName = "sizes")]
public class Sizes
{
[XmlElement(ElementName = "size")]
public List<Size> Size { get; set; }
}
[XmlRoot(ElementName = "data")]
public class Data
{
[XmlElement(ElementName = "sets")]
public Sets Sets { get; set; }
[XmlElement(ElementName = "pictures")]
public SinglePictures Pictures { get; set; }
}
Now we are already inside the listAlbums, so I defined the Binding Pictures, but then in the CachedImage I need to go inside the Picture to get the element imageSource, but how can I do that?
It seems that your page contains the bindable layout in a CollectionView.
Actually , you could use list in list as the ItemsSource of CollectionView .
in code behind
public class VM_CounterList
{
public ObservableCollection<AlbumsForList> albumList { get; set; }
public VM_CounterList()
{
albumList = new ObservableCollection<AlbumsForList>() {
new AlbumsForList(){
Name="No.1",
DemoImage = "screen.png",
Pictures = new ObservableCollection<MyImage>{ new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" } },
},
new AlbumsForList(){
Name="No.1",
DemoImage = "screen.png",
Pictures = new ObservableCollection<MyImage>{ new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" } },
},
new AlbumsForList(){
Name="No.1",
DemoImage = "screen.png",
Pictures = new ObservableCollection<MyImage>{ new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" } },
},
new AlbumsForList(){
Name="No.1",
DemoImage = "screen.png",
Pictures = new ObservableCollection<MyImage>{ new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" },new MyImage() {ImageSource="screen.png" } },
},
};
}
}
public class AlbumsForList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string Name { get; set; }
public ObservableCollection<MyImage> Pictures { get; set; }
private string demoImage;
public string DemoImage
{
set
{
if (demoImage != value)
{
demoImage = value;
NotifyPropertyChanged("DemoImage");
}
}
get { return demoImage; }
}
}
public class MyImage : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string imageSource;
public string ImageSource
{
set
{
if (imageSource != value)
{
imageSource = value;
NotifyPropertyChanged("ImageSource");
}
}
get { return imageSource; }
}
}
<DataTemplate>
<Image HeightRequest="40" WidthRequest="55" Aspect="AspectFill" Source="{Binding ImageSource}"/>
</DataTemplate>
So that you don't need to change the code in xaml , it works fine like the following screen shot .
I used local image just for demo .
I'm trying to make an expandable listview item when i press the name of an item .. the problem is that the value of IsVisiable change but that don't reflect on the page and it's stays the same (only the name of the item is shown not all the hidden details) .
First I added a IsVisiable prop to my model
public class Item
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public string Quality { get; set; }
public int Size { get; set; }
public decimal Price { get; set; }
public bool IsVisiable { get; set; }
}
This is the content page
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:XamarinApp.ViewModels;assembly=XamarinApp"
x:Class="XamarinApp.ViewModels.Views.CustomerProfilePage">
<ContentPage.BindingContext>
<viewModels:CustomerProfileViewModel/>
</ContentPage.BindingContext>
<ListView ItemsSource="{Binding Items}"
HasUnevenRows="True"
ItemTapped="ListView_OnItemTapped">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}" ></Label>
<StackLayout IsVisible="{Binding IsVisiable}">
<Label Text="{Binding Category}" ></Label>
<Label Text="{Binding Quality}" ></Label>
<Label Text="{Binding Size}" ></Label>
<Label Text="{Binding Price} "></Label>
</StackLayout>
<!-- <Label Text="المسافة بينك وبين العميل"></Label> -->
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I create a onItemTapped method
public partial class CustomerProfilePage : ContentPage
{
public CustomerProfilePage (string userId)
{
InitializeComponent ();
this.BindingContext = new CustomerProfileViewModel(userId);
}
private void ListView_OnItemTapped(object sender, ItemTappedEventArgs e)
{
var vm = BindingContext as CustomerProfileViewModel;
var Item = e.Item as Item;
vm?.HideOrShowItem(Item);
}
}
Then i added HideOrShow item method for control in my vm
public class CustomerProfileViewModel:INotifyPropertyChanged
{
public CustomerProfileViewModel()
{
}
public CustomerProfileViewModel(string cutomerId)
{
CustomerId = cutomerId;
if (GetItems.CanExecute(null))
GetItems.Execute(null);
}
public List<Item> Items
{
get => _items;
set
{
if (Equals(value, _items)) return;
_items = value;
OnPropertyChanged();
}
}
public string CustomerId { get;}
private List<Item> _items;
private Item _oldItem;
ApiServices _apiServices = new ApiServices();
public void HideOrShowItem(Item item)
{
if (_oldItem == item)
{
item.IsVisiable = !item.IsVisiable;
UpdateItems(item);
}
else
{
if (_oldItem != null)
{
_oldItem.IsVisiable = false;
UpdateItems(_oldItem);
}
item.IsVisiable = true;
UpdateItems(item);
}
_oldItem = item;
}
private void UpdateItems(Item item)
{
var index = Items.IndexOf(item);
Items.Remove(item);
Items.Insert(index, item);
}
public ICommand GetItems
{
get
{
return new Command(async () =>
{
var accesstoken = Settings.AccessToken;
Items = await _apiServices.GetItemsForSpecificUser(accesstoken, CustomerId);
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The problems I can see:
Your class Item must implement INotifyPropertyChanged interface
I also see that you can update your list Items. So to reflect the list changes in your UI, Items must be an ObservableCollection<Item>
Updated Item class (should look something like that):
public class Item : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
private bool _isVisible;
// . . .
public int Id
{
get => _id;
set
{
if (_id != value)
{
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Id)));
}
}
}
public bool IsVisible
{
get => _isVisible;
set
{
if (_isVisible != value)
{
_isVisible = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.IsVisible)));
}
}
}
// . . . Other properties
And in your ViewModel, declare the list of items:
private ObservableCollection<Item> _items;
public ObservableCollection<Item> Items{
get => _items;
private set{
_items = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Items)));
}
}
Try with this and tell us if it's ok...
I am making a UWP app and I have bound an ObservableCollection<EduRole> to a ComboBox. I can see the items populated in the ComboBox. But I also need to set it's SelectedItem property which I cant get to work.
XAML:
<ComboBox Name="EducationalLevelComboBoxUpdate"
ItemsSource="{x:Bind EduRoleList, Mode=OneWay}"
<!--IS THIS OKAY?-->
SelectedItem="{x:Bind Path=User.EduRole.Read, Mode=OneWay}"
PlaceholderText="Select educational role">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="viewModels:EduRoleViewModel">
<TextBlock Text="{x:Bind Read}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Source:
private ObservableCollection<EduRoleViewModel> EduRoleList { get; set; } = new ObservableCollection<EduRoleViewModel>();
ViewModel:
public class EduRoleViewModel
{
public string Key { get; set; }
public string Read { get; set; }
}
User is object of a class called UserViewModel that has EduRoleViewModel type property here's that class:
class UserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private EduRoleViewModel _eduRole;
public EduRoleViewModel EduRole
{
get { return _eduRole; }
set
{
_eduRole = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I want the Read property of the EduRole property of UserViewModel to be the SelectedItem of the ComboBox
I am trying to set the ItemsSource of a UWP ComboBox to a property of the ViewModel, but I get an error:
Error: BindingExpression path error: 'componentsLookup' property not found on 'Orders.Component'
The relevant bit of XAML looks like this:
<Page.DataContext>
<local:OrderPageViewModel x:Name="OrderPageViewModel" />
</Page.DataContext>
<ListView
Name="ComponentsList"
ItemsSource="{Binding Components}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox
ItemsSource="{Binding componentsLookup,Mode=TwoWay}"
DisplayMemberPath="ComponentCode"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The relevant bit of the ViewModel looks like this:
public class OrderPageViewModel
{
public ObservableCollection<Product> Products { get; set; } = new ObservableCollection<Product>();
public List<Component> componentsLookup = new List<Component>();
Edit 1: The models look like this
public class Product
{
public string ProductCode { get; set; }
public string ProductDescription { get; set; }
public List<Component> Components { get; set; }
public override string ToString()
{
return this.ProductCode;
}
}
public class Component
{
public Guid ComponentId { get; set; }
public Product Product { get; set; }
public string ComponentCode { get; set; }
public string ComponentDescription { get; set; }
public string ComponentColor { get; set; }
public decimal ComponentHeight { get; set; }
public decimal ComponentWidth { get; set; }
public override string ToString()
{
return this.ComponentCode;
}
}
How do I set the ItemsSource to componentsLookup
Nested binding is what you actually want to do. Since the ComboBox is nested inside the ListView, ItemsSource of ComboBox need to be a sub collection of ListView. componentsLookup should be a property of class Orders.Component in your code snippet. You can use a nested source structure like follows for binding:
public class OrderPageViewModel
{
public ObservableCollection<Product> Products { get; set; } = new ObservableCollection<Product>()
{
new Product
{
productname="productname",
componentsLookup=new List<Component>
{
new Component {componentname="test1" },
new Component {componentname="test2" }
}
},
new Product
{
componentsLookup=new List<Component>
{
new Component {componentname="test1" },
new Component {componentname="test2" }
}
}
};
}
public class Component
{
public string componentname { get; set; }
}
public class Product
{
public string productname { get; set; }
public List<Component> componentsLookup { get; set; }
}
XAML Code
<Page.DataContext>
<local:OrderPageViewModel x:Name="OrderPageViewModel" />
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" >
<ListView Name="ComponentsList" ItemsSource="{Binding Products}" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding productname}"></TextBlock>
<ComboBox DisplayMemberPath="ComponentCode" ItemsSource="{Binding componentsLookup, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
More details please reference the official data binding document.