Updating a ListView with Xamarin.Forms - xamarin.ios

I am having an issue with list views on in a couple of my Xamarin Forms applications. One form is within a tabbed page setup, the other is a normal content page (different apps)
I have a class like this
public class SomeClass
{
public string StringOne {get;set;}
public string StringTwo {get;set;}
public int IntOne {get;set;}
}
In my Content page, I set up an ObservableCollection and add some data in. I then tell the list that SomeClass is my ItemSource. This produces the ListView correctly on all of my devices.
The problem is that when I change one of the properties, nothing on the ListView changes (so if say I have 3 objects in the Observable and remove one, the list still says 3 - or if I change a property in my second object, the second item on the ListView doesn't change either).
I have also tried to solve the problem by using a standard List and implement INotifyChanged within the class. Again though, the ListView doesn't alter when the List changes.
I know the data has altered as if I make a change to the object, come out and go back in, the data has changed in the UI.
Am I doing something wrong or is this a bug I need to putting into Bugzilla?

It will not change if you don't bind it and implement INotifyPropertyChanged interface.
Sample Code:
public class ObservableProperty : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SomeClass:ObservableProperty
{
string stringOne;
string stringTwo;
int intOne;
public string StringOne
{
get{return stringOne;}
set
{
stringOne = value;
OnPropertyChanged("StringOne");
}
}
public string StringTwo
{
get{ return stringTwo;}
set
{
stringTwo = value;
OnPropertyChanged("StringTwo");
}
}
public int IntOne
{
get{ return intOne;}
set
{
intOne = value;
OnPropertyChanged("IntOne");
}
}
}
public class MainVM:ObservableProperty
{
ObservableCollection<SomeClass> items;
public ObservableCollection<SomeClass> items
{
get{return items;}
set
{
items = value;
OnPropertyChanged("Items");
}
}
public MainVM()
{
Items = new ObservableCollection<SomeClass>();
Items.Add(new SomeClass(){StringOne = "123", StringTwo = "test", IntOne =12});
}
public void CallMeForChangingProperty()
{
SomeClass item = Items[0];
item.StringOne = "Test1";
}
}
public class MainView
{
public MainView()
{
this.BindingContext= new MainVM()
}
}
< ListView ItemsSource="{Binding Items}" RowHeight="120">
< ListView.ItemTemplate>
< DataTemplate>
< ViewCell>
< ViewCell.View>
< StackLayout>
< Label Text= "StringOne" />
< Label Text= "StringTwo" />
< Label Text= "IntOne" />
</ StackLayout>
</ ViewCell.View>
</ ViewCell>
</ DataTemplate>
</ ListView.ItemTemplate>
</ ListView>

Answer given by #eakgul works like a charm for me.
I'll attach here what I've implemented, maybe it could help someone.
You have to set INotifyPropertyChanged both, to the ObservableColection and to it's itens.
I have a BaseViewModel with INotifyPropertyChanged as follows:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void SetProperty<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals( backingField, value)) return;
backingField = value;
OnPropertyChanged(propertyName);
}
}
On my BluetoothPage.xaml, first I set bindincontext to my BluetoothPageViewModel.cs and set the ListView ItemsSource and it's binded labels:
<ContentPage.BindingContext>
<viewmodel:BluetoothPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="5,10">
<Button x:Name="Scan_Devices_Button"
Command="{Binding SearchNew_Button_Clicked}"/>
<ListView x:Name="DevicesList"
ItemsSource="{Binding BluetoothDevices}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
IsPullToRefreshEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Text="{Binding device.Device.NativeDevice.Name}"/>
<Label Grid.Column="1"
Text="{Binding device.Device.NativeDevice.Address, StringFormat='ID: {0}'}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Text="{Binding device.Rssi, StringFormat='Power: {0:F2}dbm'}"/>
<Label Grid.Column="1"
Text="{Binding distance, StringFormat='Distance: {0:F2}m'}"/>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
Then, in my BluetoothPageViewModel.cs I extend it with the BaseViewModel and declare ItemsSource BluetoothDevices with INotifyPropertyChanged. At this moment, everytime I change an item on the ObservableCollection BluetoothDevices, the ListView will be updated. But, If I made a change on an item inside the ObservableCollection, nothing will happen!
So, you must set INotifyPropertyChanged to it's itens.
Following is my BluetoothPageViewModel, which uses a class BluetoothPageModel in the PageModel BluetoothPageModel.cs
The BluetoothPageViewModel:
public class BluetoothPageViewModel : BaseViewModel
{
public BluetoothPageViewModel()
{
SearchNew_Button_Clicked = new Command(NewDevices_Button_Clicked_Event);
Scan_Devices_Button_BgColor = "#D6D7D7";
Scan_Devices_Button_Text = "Scan nearby devices";
}
#region Declarations
public List<IDevice> iDeviceList = new List<IDevice>();
public ObservableCollection<BluetoothPageModel> _bluetoothDevices = new ObservableCollection<BluetoothPageModel>();
public BluetoothPageModel _selectedItem;
public ObservableCollection<BluetoothPageModel> BluetoothDevices
{
get { return _bluetoothDevices; }
set { SetProperty(ref _bluetoothDevices, value); }
}
public BluetoothPageModel SelectedItem
{
get { return _selectedItem; }
set { SetProperty(ref _selectedItem, value); }
}
public ICommand SearchNew_Button_Clicked { get; private set; }
#endregion
#region Functions
private void NewDevices_Button_Clicked_Event(object obj)
{
// discover some devices
if (!CrossBleAdapter.Current.IsScanning)
{
BluetoothDevices.Clear();
iDeviceList.Clear();
var scanner = CrossBleAdapter.Current.Scan().Subscribe(scanResult =>
{
if (!iDeviceList.Contains(scanResult.Device))
{
iDeviceList.Add(scanResult.Device);
Device.BeginInvokeOnMainThread(() =>
{
BluetoothDevices.Add(new BluetoothPageModel
{
device = scanResult,
distance = Math.Pow(10, ((-68 - scanResult.Rssi) / 31.1474))
});
});
}
else
{
int ind = iDeviceList.IndexOf(scanResult.Device);
Device.BeginInvokeOnMainThread(() =>
{
BluetoothDevices[ind].device = scanResult;
BluetoothDevices[ind].distance = Math.Pow(10, ((-68 - scanResult.Rssi) / 31.1474));
});
}
});
}
else
{
CrossBleAdapter.Current.StopScan(); //When you want to stop scanning
}
}
#endregion
}
Finally, to be able to update data when you change a property of the BluetoothPageModel class:
public class BluetoothPageModel:BaseViewModel
{
public IScanResult _device;
public double _distance;
public IScanResult device
{
get { return _device; }
set { SetProperty(ref _device, value); }
}
public double distance
{
get { return _distance; }
set { SetProperty(ref _distance, value); }
}
}
Thanks to eakgul answer I could get it working. Hope it can help someone else.

Related

.NET MAUI - CollectionView not updating data [duplicate]

I have code that is a sample bowling recap where 3 games are entered and are summarized in a Series column. The problem that I am experiencing is the Series column is not getting updated as I anticipate. Obviously, I am doing something wrong but can not see what I am doing wrong. Below is the code that supports the application:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="HoriView.MainPage">
<StackLayout>
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<Image
Source="dotnet_bot.png"
HeightRequest="200"
HorizontalOptions="Center" />
<Label
Text="Hello, Bowlers!"
FontSize="32"
HorizontalOptions="Center" />
<Label
Text="Bowling Recap Snippet"
FontSize="18"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ScrollView>
<ScrollView>
<HorizontalStackLayout HorizontalOptions="Center">
<CollectionView SelectionMode="Single" ItemsSource="{Binding scores}" ItemsLayout="HorizontalList">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding Game}" FontSize="Medium" WidthRequest="83" VerticalOptions="Center" HorizontalOptions="EndAndExpand" HorizontalTextAlignment="End"></Label>
<Entry Grid.Row="1" Text="{Binding Score}" TextChanged="UpdateGame" Keyboard="Numeric" ReturnType="Next" Placeholder="Game" WidthRequest="83" FontSize="Medium" ></Entry>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</HorizontalStackLayout>
</ScrollView>
</StackLayout>
</ContentPage>
using System.Collections.ObjectModel;
namespace HoriView;
public partial class MainPage : ContentPage
{
public ObservableCollection<Games> scores { get; set; }
string header = "Game";
public MainPage()
{
InitializeComponent();
scores= new ObservableCollection<Games>();
for(int i=0; i<4; i++)
{
if (i == 3)
{
header = "Series";
}
else
{
header = "Game" + (i+1).ToString();
}
scores.Add(new Games()
{
Game=header,
Score=0
});
}
BindingContext = this;
}
public void UpdateGame(object sender, TextChangedEventArgs e)
{
scores[scores.Count-1].Score = 0;
for(int i=0; i< scores.Count-1; i++)
{
scores[scores.Count - 1].Score += scores[i].Score;
}
}
}
public class Games
{
public string Game { get; set; }
public int Score { get; set; }
}
Any help would be appreciated. Thanks.
Your Games class doesn't implement the INotifyPropertyChanged interface.
You can either implement that manually:
public class Games : INotifyPropertyChanged
{
private string _game;
public string Game
{
get => _game;
set
{
if(_game == value) return;
_game = value;
OnPropertyChanged();
}
}
private int _score;
public int Score
{
get => _score;
set
{
if(_score == value) return;
_score = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Alternatively, you could also inherit from ObservableObject from the MVVM Community Toolkit and then simply write the following using Source Generators:
public partial class Games : ObservableObject
{
[ObservableProperty]
private string _game;
[ObservableProperty]
private int _score;
}
In this case, you only define the backing fields and the Source Generators will create the uppercase, observable properties Game and Score for you.

Search by list Xamarin Forms (MVVM)

I have a problem with searching records. As you type characters, the list will not update.
At the moment I am using Binding Login for testing but I will be using Uri with a photo.
My ViewModel
private ObservableCollection<FollowerModel> _followerItems = null;
public ObservableCollection<FollowerModel> FollowerItems
{
get => _followerItems;
set
{
_followerItems = value;
OnPropertyChanged();
OnPropertyChanged();
}
}
private FollowerModel _followerItem = null;
public FollowerModel FollowerItem
{
get => _followerItem;
set
{
_followerItem = value;
OnPropertyChanged();
OnFollowerItemChanged();
}
}
// ...
private void OnSearchPhrase()
{
var searchPhrase = _searchPhrase.Trim();
if (searchPhrase.Length < SEARCH_PHRASE_MIN_LENGTH) return; // SEARCH_PHRASE_MIN_LENGTH = 3
FollowerItems.Where(name => name.Login.Contains(searchPhrase));
}
My PageView
<flv:FlowListView SeparatorVisibility="None" HasUnevenRows="true" FlowItemTappedCommand="{Binding FollowerItemCommand}" FlowColumnMinWidth="100" FlowItemsSource="{Binding FollowerItems}">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<Grid Padding="5">
<Grid.RowDefinitions>
<RowDefinition Height="98" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="98" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Login}" />
</Image>-->
</Grid>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
First add a search bar in your xaml
<SearchBar Placeholder="Search" Text="{Binding SearchText}" />
Then Create a Bindable property for it
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
_searchText = value;
SearchUserText(_searchText);
RaisePropertyChanged("SearchText");
}
}
Then Create a function SearchUserText that will listen for text and change your list
public void SearchUserText(string text)
{
var searchPhrase = _searchPhrase.Trim();
if (searchPhrase.Length < SEARCH_PHRASE_MIN_LENGTH) return; // SEARCH_PHRASE_MIN_LENGTH = 3
FollowerItems.Where(name => name.Login.Contains(searchPhrase));
}
NB: Don't forget to add getters and setters on your model FollowerModel
example:
public class FollowerModel : BindableBase
{
private int _id;
public int Id
{
get { return _id; }
set { SetProperty(ref _id, value); }
}
private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
}
First of all, you will need two ObservableCollections:
One ObservableCollection which contains all items
One ObservableCollection which contains the filtered items
The filtered ObservableCollection will be the one which is binded to your control. So you only need a private variable for all items and a public property for the filtered items:
private ObservableCollection<FollowerModel> _allFollowerItems;
private ObservableCollection<FollowerModel> _followerItems;
public ObservableCollection<FollowerModel> FollowerItems
{
get { return _followerItems}
set
{
_followerItems = value;
RaisePropertyChanged();
}
}
One more thing I noticed is that you are using OnPropertyChanged(). It is deprecated, use RaisePropertyChanged() instead.
In your OnSearchPhrase() method, you want to set FollowerItems to the original items (_allFollowerItems) if searchPhrase.Length < SEARCH_PHRASE_MIN_LENGTH. Otherwise, you will filter it:
private void OnSearchPhrase()
{
var searchPhrase = _searchPhrase.Trim();
if (searchPhrase.Length < SEARCH_PHRASE_MIN_LENGTH)
{
FollowerItems = _allFollowerItems;
}
else
{
FollowerItems = new ObservableCollection<FollowerModel>(_allFollowerItems.Where(name => name.Login.ToLower().Contains(searchPhrase.ToLower())));
}
}
Notice that I added ToLower() to the strings to have a more user friendly search. If Case-sensetive search was intended, just remove it.
Don't forget to set FollowerItems and _allFollowerItems once when at the place where you are loading your items right now:
FollowerItems = new ObservableCollection(items);
_allFollowerItems = new ObservableCollection(items);

Specify default ItemTemplate

I have a WPF custom Control in which I have a Listview. The control has dependancy properties for the ItemSource and ItemTemplate of the ListView. This all works fine. What I would like to do is to be able to set a default ItemTemplate so that I don't end up with object.ToString() for the Items in the Listview.
Below is the Xaml Style for my control.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:btl="clr-namespace:Btl.Controls"
mc:Ignorable="d">
<DataTemplate x:Key="DefaultListViewItem" DataType="btl:SelectableItem">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="2" IsChecked="{Binding Selected}" />
<TextBlock Margin="5,2" Text="{Binding Description}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
<Style TargetType="{x:Type btl:SelectItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type btl:SelectItemsControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="PART_Title" Margin="10"
Text="{Binding Path=Title,
RelativeSource={RelativeSource TemplatedParent}}"
TextWrapping="Wrap"
Visibility="Collapsed"/>
<GroupBox Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="PART_EnabledCheck" Margin="0,5"
Content=""
IsChecked="{Binding Path=EnabledCheck, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"/>
<TextBlock x:Name="PART_GroupTitle" VerticalAlignment="Center"
Text="{Binding Path=GroupTitle,
RelativeSource={RelativeSource TemplatedParent}}"/>
</StackPanel>
</GroupBox.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<ListView x:Name="PART_Items"
ItemsSource="{Binding ItemSourceList,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type btl:SelectItemsControl}}}"
ItemTemplate="{Binding ItemTemplate,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type btl:SelectItemsControl}}}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2"
>
</ListView>
</DockPanel>
<CheckBox x:Name="PART_SelectAllCheck" Grid.Row="1" Margin="11,5" Content="Select All"
IsChecked="{Binding Selected}"/>
</Grid>
</GroupBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here is my control
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace Btl.Controls
{
/// <summary>
///
/// </summary>
///
[TemplatePart(Name = "PART_Title", Type = typeof(TextBlock))]
[TemplatePart(Name = "PART_EnabledCheck", Type = typeof(CheckBox))]
[TemplatePart(Name = "PART_GroupTitle", Type = typeof(TextBlock))]
[TemplatePart(Name = "PART_Items", Type = typeof(ListView))]
[TemplatePart(Name = "PART_SelectAllCheck", Type = typeof(CheckBox))]
public class SelectItemsControl : UserControl
{
#region DependencyProperties
#region Title
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(SelectItemsControl), new PropertyMetadata(string.Empty,
OnTitleChanged
));
private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as TextBlock;
if (control != null)
if (string.IsNullOrEmpty(e.NewValue.ToString()))
{
control.Visibility = string.IsNullOrEmpty(e.NewValue.ToString()) ? Visibility.Collapsed : Visibility.Visible;
}
}
#endregion
#region HasEnabledCheck
public bool HasEnabledCheck
{
get { return (bool)GetValue(HasEnabledCheckProperty); }
set { SetValue(HasEnabledCheckProperty, value); }
}
// Using a DependencyProperty as the backing store for EnabledCheck. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HasEnabledCheckProperty =
DependencyProperty.Register("HasEnabledCheck", typeof(bool), typeof(SelectItemsControl), new UIPropertyMetadata(false));
#endregion
#region EnabledCheck
public bool EnabledCheck
{
get { return (bool)GetValue(EnabledCheckProperty); }
set { SetValue(EnabledCheckProperty, value); }
}
// Using a DependencyProperty as the backing store for EnabledCheck. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnabledCheckProperty =
DependencyProperty.Register("EnabledCheck", typeof(bool), typeof(SelectItemsControl), new UIPropertyMetadata(true));
#endregion
#region GroupTitle
public string GroupTitle
{
get { return (string)GetValue(GroupTitleProperty); }
set { SetValue(GroupTitleProperty, value); }
}
// Using a DependencyProperty as the backing store for GroupTitle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GroupTitleProperty =
DependencyProperty.Register("GroupTitle", typeof(string), typeof(SelectItemsControl), new UIPropertyMetadata(""));
#endregion
#region ItemSourceList
public IEnumerable<ISelectable> ItemSourceList
{
get { return (IEnumerable<ISelectable>)GetValue(ItemSourceListProperty); }
set { SetValue(ItemSourceListProperty, value); }
}
// Using a DependencyProperty as the backing store for Items. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemSourceListProperty =
DependencyProperty.Register("ItemSourceList", typeof(IEnumerable), typeof(SelectItemsControl));
#endregion
#region ItemTemplate
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(SelectItemsControl),
new UIPropertyMetadata(default(DataTemplate)));
#endregion
#region DescriptionTemplate
public DataTemplate DescriptionTemplate
{
get { return (DataTemplate)GetValue(DescriptionTemplateProperty); }
set { SetValue(DescriptionTemplateProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DescriptionTemplateProperty =
DependencyProperty.Register("DescriptionTemplate", typeof(DataTemplate), typeof(SelectItemsControl),
new UIPropertyMetadata(default(DataTemplate), OnDescriptionTemplateChanged));
private static void OnDescriptionTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
#endregion
#region ItemSelected
public bool ItemSelected
{
get { return (bool)GetValue(ItemSelectedProperty); }
set { SetValue(ItemSelectedProperty, value); }
}
// Using a DependencyProperty as the backing store for EnabledCheck. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemSelectedProperty =
DependencyProperty.Register("ItemSelectedCheck", typeof(bool), typeof(SelectItemsControl), new UIPropertyMetadata(false));
#endregion
#region ItemDescription
public string ItemDescription
{
get { return (string)GetValue(ItemDescriptionProperty); }
set { SetValue(ItemDescriptionProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemDescription. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemDescriptionProperty =
DependencyProperty.Register("ItemDescription", typeof(string), typeof(SelectItemsControl), new UIPropertyMetadata(""));
#endregion
#region SelectAllCheck
public bool SelectAll
{
get { return (bool)GetValue(SelectAllProperty); }
set { SetValue(SelectAllProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectAll. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectAllProperty =
DependencyProperty.Register("SelectAll", typeof(bool), typeof(SelectItemsControl), new UIPropertyMetadata(false));
#endregion
#endregion
#region Private Members
private TextBlock _partTitle;
private CheckBox _partEnabledCheck;
private TextBlock _partGroupTitle;
private ListView _partItemsListView;
private CheckBox _partSelectAllCheck;
#endregion
/// <summary>
///
/// </summary>
static SelectItemsControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SelectItemsControl),
new FrameworkPropertyMetadata(typeof(SelectItemsControl)));
}
public SelectItemsControl()
{
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
if (ItemTemplate == null)
{
CreateDefaultItemTemplate();
}
PresentationSource presentationSource = PresentationSource.FromVisual((Visual)sender);
// Subscribe to PresentationSource's ContentRendered event
// ReSharper disable once PossibleNullReferenceException
presentationSource.ContentRendered += SelectItemsControl_ContentRendered;
}
private void SelectItemsControl_ContentRendered(object sender, EventArgs e)
{
// Don't forget to unsubscribe from the event
((PresentationSource)sender).ContentRendered -= SelectItemsControl_ContentRendered;
ListenToSelectedCheckBoxClickEvent(_partItemsListView, true);
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
ListenToSelectedCheckBoxClickEvent(_partItemsListView, false);
}
private void CreateDefaultItemTemplate()
{
DataTemplate template = new DataTemplate { DataType = typeof(ListViewItem) };
FrameworkElementFactory stackPanelFactory = new FrameworkElementFactory(typeof(StackPanel));
stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
FrameworkElementFactory selected = new FrameworkElementFactory(typeof(CheckBox));
selected.SetBinding(TextBlock.TextProperty, new Binding("Selected"));
stackPanelFactory.AppendChild(selected);
FrameworkElementFactory title = new FrameworkElementFactory(typeof(TextBlock));
title.SetBinding(TextBlock.TextProperty, new Binding("Description"));
stackPanelFactory.AppendChild(title);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Code to get the Template parts as instance member
_partTitle = GetTemplateChild("PART_Title") as TextBlock;
_partEnabledCheck = GetTemplateChild("PART_EnabledCheck") as CheckBox;
_partGroupTitle = GetTemplateChild("PART_GroupTitle") as TextBlock;
_partItemsListView = GetTemplateChild("PART_Items") as ListView;
//_partItemSelectedCheck = GetTemplateChild("PART_ItemSelectedCheck") as CheckBox;
_partSelectAllCheck = GetTemplateChild("PART_SelectAllCheck") as CheckBox;
if (_partTitle == null || _partEnabledCheck == null || _partGroupTitle == null || _partItemsListView == null ||
_partSelectAllCheck == null)
{
throw new NullReferenceException("Template parts not available");
}
// set visibility
_partEnabledCheck.Visibility = HasEnabledCheck ? Visibility.Visible : Visibility.Collapsed;
_partEnabledCheck.Click += PartEnabledCheckOnClick;
_partTitle.Visibility = string.IsNullOrEmpty(_partTitle.Text) ? Visibility.Collapsed : Visibility.Visible;
_partGroupTitle.Visibility = string.IsNullOrEmpty(_partGroupTitle.Text) ? Visibility.Collapsed : Visibility.Visible;
_partSelectAllCheck.Click += PartSelectAllCheckOnClick;
}
private void PartEnabledCheckOnClick(object sender, RoutedEventArgs routedEventArgs)
{
_partItemsListView.IsEnabled = EnabledCheck;
_partSelectAllCheck.IsEnabled = EnabledCheck;
}
private void ListenToSelectedCheckBoxClickEvent(DependencyObject parent, bool set)
{
foreach (CheckBox cb in VisualTreeHelpers.FindVisualChildren<CheckBox>(parent))
{
BindingExpression binding = cb.GetBindingExpression(CheckBox.IsCheckedProperty);
// ReSharper disable once PossibleNullReferenceException
if (binding.ParentBinding.Path.Path == "Selected")
{
if (set)
cb.Click += SelectedCheckBox_Click;
else
cb.Click -= SelectedCheckBox_Click;
}
}
}
private void SelectedCheckBox_Click(object sender, RoutedEventArgs e)
{
_partSelectAllCheck.IsChecked = !ItemSourceList.AsQueryable().Any(x => x.Selected == false);
}
private void PartSelectAllCheckOnClick(object sender, RoutedEventArgs routedEventArgs)
{
foreach (CheckBox cb in VisualTreeHelpers.FindVisualChildren<CheckBox>(_partItemsListView))
{
BindingExpression binding = cb.GetBindingExpression(CheckBox.IsCheckedProperty);
// ReSharper disable once PossibleNullReferenceException
if (binding.ParentBinding.Path.Path == "Selected")
{
cb.IsChecked = _partSelectAllCheck.IsChecked ?? false;
}
}
}
}
}
Could someone please post some code which shows how set - create the default template?
This turned out to be simpler than I thought. Because the ItemTemplate is bound to a dependency property I can specify the default template there. That just left the creation of the template. See below.
#region ItemTemplate
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(SelectItemsControl),
new UIPropertyMetadata(DefaultItemTemplate));
private static DataTemplate DefaultItemTemplate
{
get
{
// tried using a MemoryStream - StreamWriter but was getting a
// "Root element missing error", would be nice to know why.
var sb = new StringBuilder();
sb.Append("<DataTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">");
sb.Append("<StackPanel Orientation=\"Horizontal\">");
sb.Append("<CheckBox Margin=\"2\" IsChecked=\"{Binding Selected}\" />");
sb.Append("<TextBlock Margin=\"5,2\" Text=\"{Binding Description}\" VerticalAlignment=\"Center\"/>");
sb.Append("</StackPanel>");
sb.Append("</DataTemplate>");
var myByteArray = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
var ms = new MemoryStream(myByteArray);
return (DataTemplate) XamlReader.Load(ms);
}
}
#endregion

creating buttons dynamically from observable collection

I have an observable collection OBSCollection which i am parsing. In this collection i am checking if the name property is "critical" and if it is "critical" , then i am trying to create red buttons dynamically for each occurence of property.
if (OBSCollection.Any(p => p.Name == "Critical"))
{
criticalcount = OBSCollection.Where(x => x.Name == "Critical").ToList().Count;
for (int i = 0; i < criticalcount; i++)
{
Button temp = new Button();
temp.Background = new SolidColorBrush(Windows.UI.Colors.Red);
temp.Width = 200;
temp.Height = 100;
temp.Content = "Critical";
CriticalPanel.Children.Add(temp);
temp.Tapped += new TappedEventHandler(bTapped_Tapped);
}
private void bTapped_Tapped(object sender, TappedRoutedEventArgs e)
{
var toremovecritical = OBSCOllection.Where(x => x.Name == "critical").First();
uiElements.Remove(toremovecritical);
}
Now the above code works only if there is one occurrence of "Critical" property. How can i rewrite the code to work for multiple occurrences and hence create multiple buttons?
Also after displaying the buttons , if a user clicks on a button the buttons visible property should be collapsed and that particular item should be removed from observable collection. I am able to remove the button from observable collection but i cannot set the visibility property of button to false from bTapped_Tapped handler. Is there anyway to resolve this?
Here is a REALLY basic example using MVVM.
XAML:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<GridView ItemsSource="{Binding Things}">
<GridView.ItemTemplate>
<DataTemplate>
<Button Width="100" Height="100" Background="Red" Content="{Binding Name}" Click="CriticalClick" DataContext="{Binding}"></Button>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
ViewModel & Model:
public class ThingViewModel
{
public ObservableCollection<Thing> Things { get; set; }
public ThingViewModel()
{
var allThings = new List<Thing>();
for (int i = 0; i < 30; i++)
{
if (i % 2 == 0)
allThings.Add(new Thing { Name = "Critical" });
else
allThings.Add(new Thing { Name = "`NonCritical" });
}
this.Things = new ObservableCollection<Thing>(allThings.Where(x => x.Name == "Critical"));
}
}
public class Thing
{
public string Name { get; set; }
}
Code Behind:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.DataContext = new ThingViewModel();
}
private void CriticalClick(object sender, RoutedEventArgs e)
{
var tappedThing = (Thing) ((Button) sender).DataContext;
((ThingViewModel) this.DataContext).Things.Remove(tappedThing);
}
This is a really simple example of how to do it with binding and MVVM. It gives you a starting point for what you are asking for though.
Hope it helps.

Property binding in MVVM

my cpde in ModelView :
public Boolean EnableTextBox { get; set; }
public CustomerAccountVM()
{
this.EnableTextBox = false;
//...
}
code in View:
XAML :
<TextBox Text="{Binding Path=IdCustomer, Mode=Default}" IsEnabled="{Binding Path=EnableTextBox,Mode=Default}" />
Why the code does not work?
no answer ?
You're not publishing the fact that the Enable property has been updated.
You need to implement the INotifyPropertyChanged interface and change your property to be:
private Boolean _enableTextBox;
public Boolean EnableTextBox
{
get { return _enableTextBox; }
set
{
_enableTextBox = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
You should wrap the PropertyChanged code in a method so you're not repeating yourself.

Resources