creating buttons dynamically from observable collection - c#-4.0

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.

Related

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);

Xamarin Button Command with Keyboard Open

I am working on an Xamarin.Forms project specifically for the iOS platform. I have an Editor control and a Button control next to each other. When I focus the editor, enter some text, and click the button it appears the command is not being fired but rather the keyboard is simply closing. I then have to tap the add button again for the command to be fired.
<StackLayout Orientation="Horizontal">
<Editor HorizontalOptions="FillAndExpand"
Text="{Binding EditorText}"/>
<Button Text="Add"
Command="{Binding AddCommand}"/>
</StackLayout>
I have tried creating a custom renderer that prevents the keyboard from closing initially and then close it after a delay. That allows the command to be fired, but I am stuck with the keyboard being open.
public class KeyboardEditorRenderer : EditorRenderer
{
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == VisualElement.IsFocusedProperty.PropertyName)
{
if (Control != null)
{
Control.ShouldEndEditing = (UITextView textField) =>
{
Task.Delay(10).ContinueWith(_ =>
{
// THIS DOES NOT WORK
textField.EndEditing(true);
});
return false;
};
}
}
base.OnElementPropertyChanged(sender, e);
}
}
My ideal solution is that you are able to enter text, tap the add button, and the keyboard closes and the command executes simultaneously. Any ideas on how to achieve this?
EDIT: It turns out the problem is with the custom renderer I use for the page. The custom renderer resizes the page when the keyboard appears so that it does not cover my editor field.
public class KeyboardPageRenderer : PageRenderer
{
private bool keyboardShowing;
private NSObject keyboardWillShow;
private NSObject keyboardWillHide;
private double duration;
private UIViewAnimationCurve curve;
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.keyboardWillShow = UIKeyboard.Notifications.ObserveWillShow(this.KeyboardWillShow);
this.keyboardWillHide = UIKeyboard.Notifications.ObserveWillHide(this.KeyboardWillHide);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
this.keyboardWillShow.Dispose();
this.keyboardWillHide.Dispose();
}
private void KeyboardWillShow(object sender, UIKeyboardEventArgs args)
{
if (!this.keyboardShowing)
{
this.keyboardShowing = true;
var keyboardFrame = UIKeyboard.FrameBeginFromNotification(args.Notification);
this.duration = args.AnimationDuration;
this.curve = args.AnimationCurve;
this.ScrollTheView(true, keyboardFrame.Height);
}
}
private void KeyboardWillHide(object sender, UIKeyboardEventArgs args)
{
if (this.keyboardShowing)
{
this.keyboardShowing = false;
var keyboardFrame = UIKeyboard.FrameBeginFromNotification(args.Notification);
this.duration = args.AnimationDuration;
this.curve = args.AnimationCurve;
this.ScrollTheView(false, keyboardFrame.Height);
}
}
private void ScrollTheView(bool scale, nfloat scrollAmount)
{
UIView.BeginAnimations(string.Empty, IntPtr.Zero);
UIView.SetAnimationDuration(this.duration);
UIView.SetAnimationCurve(this.curve);
var frame = View.Frame;
// Assumes the page belongs to a tabbed view.
// This does not scale to pages that do not have one.
UITabBarController tabBarController = new UITabBarController();
nfloat tabHeight = tabBarController.TabBar.Frame.Size.Height;
scrollAmount -= tabHeight;
if (scale)
{
frame.Y -= scrollAmount;
}
else
{
frame.Y += scrollAmount;
}
View.Frame = frame;
UIView.CommitAnimations();
}
}
There is two issues in your approach
After Task.Delay(10), you are not on the UI thread anymore, which means you have to use Device.BeginInvokeOnMainThread in order to access UI elements.
Control.ShouldEndEditing must be cleared before you call EndEditing
A working solution would look like this:
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Element == null || Control == null)
return;
VisualElement element = Element as VisualElement;
if (element == null)
return;
if (e.PropertyName == VisualElement.IsFocusedProperty.PropertyName && element.IsFocused == false)
{
Control.ShouldEndEditing = (UITextView control) =>
{
Device.BeginInvokeOnMainThread(() =>
{
control.ShouldEndEditing = null;
control.EndEditing(true);
});
// prevent the keyboard from closing
return false;
};
}
}

ObservableCollection - Changes on the dataset do not get updated

I am new to programming for winrt. I am using VS 2015, trying to do some testing on ObservableCollection to see how the change in the collection got reflected on the UI. I must have done something incorrectly. Just do not know what.
This is my model:
class MyItems
{
public int ItemID { get; set; }
public string ItemDescription { get; set; }
}
class MyItemList:List<MyItems>
{
public MyItemList()
{
Random r = new Random(DateTime.Now.Day);
for (int i = 0; i < r.Next(10)+1; i++)
{
this.Add(new MyItems() { ItemID = i + 1,
ItemDescription = string.Format("Item_{0}", i + 1) });
}
}
public ObservableCollection<MyItems> getEven()
{
return new ObservableCollection<MyItems>(this.Where(x=>x.ItemID%2==0).ToList());
}
public void AddMoreItems(int v)
{
int total = this.Count;
for (int i = 0 ; i < v; i++)
{
this.Add(new MyItems() { ItemID = total + i, ItemDescription = string.Format("Item_{0}", total+i) });
}
}
}
On MainPage.xaml, I have a button to add items to the list. I have created a listview programmatically and binding to the dataset two ways.
public sealed partial class MainPage : Page
{
static MyItemList myItems = new MyItemList();
public MainPage()
{
this.InitializeComponent();
var t = myItems.getEven();
ListView myListView = new ListView() { ItemTemplate = (DataTemplate)Resources["myItemTemplate"] };
myListView.DataContext = t;
var binding = new Binding();
binding.Source = t;
binding.Mode = BindingMode.TwoWay;
myListView.SetBinding(ListView.ItemsSourceProperty, binding);
MyGrid.Children.Add(myListView);
}
private void AddItems_Click(object sender, RoutedEventArgs e)
{
myItems.AddMoreItems(3);
}
}
When I clicked on the button, 3 more items are added but they are not reflected in my listview. Something else needs to be done beside using the ObservableCollection and set binding to twoWays?
Your problem, essentially, is here:
public ObservableCollection<MyItems> getEven()
{
return new ObservableCollection<MyItems>(this.Where(x=>x.ItemID%2==0).ToList());
}
When you query this and call ToList(), you are creating a new list that is independent of the original. If you add items to the original, it's not going to be reflected in your derived list. Think of it this way:
public ObservableCollection<MyItems> getEven()
{
var filteredList = this.Where(x=>x.ItemID%2==0).ToList()
return new ObservableCollection<MyItems>(filteredList);
}
Adding to this is not going to change the contents filteredList at all.
Additionally, creating a new ObservableCollection every time you access getEven instead of modifying an existing one means that the events when adding and deleting to the observable collection will never fire.
You are using observable collections in a fundamentally incorrect way. Why not just have MyItemList derive from ObservableCollection<T> instead of List<T>?
Collection that inherits from ObservableCollection - What are the benefits?
Also, if you're trying to filter by even/odd, you should look into ICollectionView
WPF Binding filtered ObservableCollection ICollectionView to Combobox

Updating a ListView with Xamarin.Forms

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.

Display a button when I tap a long list selector

I want to display a button, when I tap a long list selector in windows phone 8.
C#
this.DisplayedContacts = new ObservableCollection<string>();
this.DataContext = this.DisplayedContacts;
var contacts = new Contacts();
contacts.SearchCompleted += (s, e) =>
{
foreach (var contact in e.Results)
{
Debug.WriteLine(contact.PhoneNumbers.Any()? contact.PhoneNumbers.First().PhoneNumber: string.Empty);
this.DisplayedContacts.Add(contact.DisplayName + " - " +
(contact.PhoneNumbers.Any()
? contact.PhoneNumbers.First().PhoneNumber
: string.Empty));
}
};
contacts.SearchAsync(string.Empty, FilterKind.DisplayName, null);
}
public ObservableCollection<string> DisplayedContacts { get; set; }
private void LongListSelector_Tap_1(object sender,GestureEventArgs e)
{}
xaml code
<phone:LongListSelector FontSize="50" Foreground="Gray" Tap="LongListSelector_Tap_1" ItemsSource="{Binding}" Margin="0,77,0,0" />
User Visibility property to hide your list and show the Button.
Your XAML will look something like bellow:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" >
<phone:LongListSelector x:Name="LongList" Tap="LongListTap"/>
<Button x:Name="ActionButton" Visibility="Collapsed"/>
</Grid>
And event handler:
private void LongListTap(object sender, GestureEventArgs e)
{
LongList.Visibility = Visibility.Collapsed;
ActionButton.Visibility = Visibility.Visible;
}
what you have to this just set the visibilty of your button to visible like suppose your buttn name is "Mybutton" ..
private void LongListSelector_Tap_1(object sender,GestureEventArgs e)
{
Mybutton.Visibility = Visibility.visible;
}

Resources