XAML Combo Box is Empty on moving back to item in collection - xaml

I have a Combo box and a Label :
<!-- Does not select appropriate value after moving back to current item in collection -->
<ComboBox
ItemsSource="{Binding Path=Items}"
SelectedValue="{Binding Path=SelectedItem, Mode=TwoWay}"
DisplayMemberPath="ItemName"
Margin="8,2,8,16" />
<!-- Displays correctly after moving back to current item in collection -->
<Label
Content="{Binding Path=SelectedItem.ItemName}"/>
I can set an Item in the combo box, but when I move from and back to the current item in the observable collection, the Combo Box does not set the SelectedValue as I expect (it remains empty) - the Label's content is set correctly - and its bound to the same thing.
What am I doing wrong?
Any help much appreciated.
Joe

Try using a ICollectionView as I described here (Answer).
The CollectionView takes care of your combobox and you can read and set the current item.
Just attach an event handler to the CurrentChanged Event from the ICollectionView.
XAML:
<ComboBox
ItemsSource="{Binding Path=Items}"
DisplayMemberPath="ItemName"
IsSynchronizedWithCurrentItem="True"
Margin="8,2,8,16" />
<Label Content="{Binding Path=CurrentItem.ItemName}"/>
ViewModel:
public class ViewModel :INotifyPropertyChanged
{
private ObservableCollection<Item> _items= new ObservableCollection<Item>();
private Item _currentItem;
public ObservableCollection<Item> Items
{
get { return _items; }
set { _items= value; OnPropertyChanged("Items");}
}
public Item CurrentItem
{
get { return _currentItem; }
set { _currentItem = value; OnPropertyChanged("CurrentItem");}
}
public ICollectionView ItemsView { get; private set; }
public ViewModel()
{
Items.Add(new Item{Id = Guid.NewGuid(), Name = "Item 1"});
Items.Add(new Item{Id = Guid.NewGuid(), Name = "Item 2"});
Items.Add(new Item{Id = Guid.NewGuid(), Name = "Item 3"});
ItemsView = CollectionViewSource.GetDefaultView(Items);
ItemsView .CurrentChanged += OnItemsChanged;
ItemsView .MoveCurrentToFirst();
}
private void OnItemsChanged(object sender, EventArgs e)
{
var selectedItem = ItemsView .CurrentItem as Item;
if (selectedItem == null) return;
CurrentItem= selectedItem ;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Item
{
public Guid Id { get; set; }
public string Name { get; set; }
}

Related

Binding to a property of the object

I have a Label and I want to bind Text to a property of an object
public class MainCar: INotifyPropertyChanged
{
string typeCar;
public string TypeCar
{
set
{
if (typeCar != value)
{
typeCar = value;
OnPropertyChanged("TypeCar");
}
}
get
{
return typeCar;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In my Xaml code I have a label and I do not understand how to bind Text of my Label to object`s property TypeCar
XAML CODE
<Label x:Name="label" FontSize="Large" Text="" />
BEHIND CODE
public Car_add()
{
NavigationPage.SetHasNavigationBar(this, false);
InitializeComponent();
this.BindingContext = new TypesCar();
}
VIEWMODEL CLASS
public class TypesCar
{
public TypesCar()
{
var vm = new MainCar() { TypeCar = "Ford" };
}
this is very well documented
<Label x:Name="label" FontSize="Large" Text="{Binding TypeCar}" />
then in the code behind
var vm = new MainCar() { TypeCar = "Ford" };
this.BindingContext = vm;
OR, if you are binding to a property on the SAME PAGE and NOT A VM
this.BindingContext = this;
note that if you want your UI to update as your VM changes, the VM must implement INotifyPropertyChanged

How to access Telerik's RadDataGrid cell content from code behind for UWP?

We have used DataGridTemplateColumn for our grid to display texbox under each column. We've a requirement to make the textboxes readonly if it contains any data (for data loading case). In order to achieve that, we need to access all text box controls under the radgrid. We've tried following approaches so far
Find all child controls using VisualTreeHelper - No textbox control was found
Tried with DataBindingComplete event
Is there any way to access the underlying cell's control from codebehind for RadDataGrid?
Alternative approach : Can we somehow user IsReadOnly property with some binding to check it's value and make the control readonly when value is there?
Can we somehow user IsReadOnly property with some binding to check it's value and make the control readonly when value is there?
Yes. You could certainly achieve this by using Binding. You just need to define a bool property and bind the IsReadOnly property of TextBox to this property. Then, you could change this bool value according to the text of TextBox.
Please refer to my following code sample for reference:
<telerikGrid:RadDataGrid x:Name="grid" AutoGenerateColumns="False" VerticalAlignment="Center">
<telerikGrid:RadDataGrid.Columns>
<telerikGrid:DataGridTemplateColumn Header="Country">
<telerikGrid:DataGridTemplateColumn.CellContentTemplate>
<DataTemplate>
<TextBox Text="{Binding Country}" HorizontalAlignment="Center" VerticalAlignment="Center" IsReadOnly="{Binding IsReadOnly}"/>
</DataTemplate>
</telerikGrid:DataGridTemplateColumn.CellContentTemplate>
</telerikGrid:DataGridTemplateColumn>
<telerikGrid:DataGridTemplateColumn Header="Flag">
<telerikGrid:DataGridTemplateColumn.CellContentTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Flag}" />
</StackPanel>
</DataTemplate>
</telerikGrid:DataGridTemplateColumn.CellContentTemplate>
</telerikGrid:DataGridTemplateColumn>
</telerikGrid:RadDataGrid.Columns>
</telerikGrid:RadDataGrid>
public sealed partial class MainPage : Page
{
ObservableCollection<Data> list = new ObservableCollection<Data>();
public MainPage()
{
this.InitializeComponent();
list.Add(new Data { Country = "Argentina",Flag="A"});
list.Add(new Data {Country=string.Empty,Flag="B"});
list.Add(new Data { Country = "China",Flag="C"});
this.grid.ItemsSource = list;
this.Loaded += MainPage_Loaded;
}
private async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
await Task.Delay(5000);
list[1].Country = "Brazil";
}
}
public class Data:INotifyPropertyChanged
{
private string _Country;
public string Country
{
get { return _Country; }
set
{
_Country = value;
if (string.IsNullOrEmpty(value))
{
IsReadOnly = true;
}
else
{
IsReadOnly = false;
}
RaisePropertyChanged("Country");
}
}
private string _Flag;
public string Flag
{
get { return _Flag;}
set
{
_Flag = value;
RaisePropertyChanged("Flag");
}
}
private bool _IsReadOnly=false;
public bool IsReadOnly
{
get { return _IsReadOnly; }
set
{
_IsReadOnly = value;
RaisePropertyChanged("IsReadOnly");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(PropertyName));
}
}
}

UWP listview binding

what is the easiest way to bind selected state of listview's item to model's boolean property?
I have model:
class Model {
public string Name { get; set; }
public bool Selected { get; set; }
}
And listview:
<ListView x:Name="myListView" SelectionMode="Multiple">
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
I bind items to the listview:
myListView.ItemsSource = // list of Model instances
I want the Selected property of Model to always reflect whether it is selected or not in myListView. So - by selecting/deselecting the item in myListView, it will hold the apropriate bool value or by setting Selected property myListView will select/deselect appropriate item.
what is the easiest way to bind selected state of listview's item to model's boolean property?
I'm not sure if this is the most easiest way, but for me I think it is the easiest way to bind SelectorItem.IsSelected property of ListViewItem to your Selected property in model. Only the problem is, we all know each item of ListView is an instance of ListViewItem, but when we use DataTemplate to build the item structure for ListViewItem, ListViewItems are not available in design-time. So my idea is to bind this property in code behind, just for example here:
<ListView x:Name="myListView" SelectionMode="Multiple" Loaded="myListView_Loaded" ItemsSource="{x:Bind Collection}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
code behind:
private ObservableCollection<Model> Collection = new ObservableCollection<Model>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
for (int i = 0; i < 100; i++)
{
Collection.Add(new Model { Name = "Name " + i });
}
}
private void myListView_Loaded(object sender, RoutedEventArgs e)
{
IEnumerable<ListViewItem> lvItems = FindVisualChildren<ListViewItem>(myListView);
if (lvItems != null)
{
foreach (ListViewItem lvitem in lvItems)
{
Model model = lvitem.Content as Model;
Binding b = new Binding
{
Source = model,
Path = new PropertyPath("Selected"),
Mode = BindingMode.TwoWay,
};
BindingOperations.SetBinding(lvitem, ListViewItem.IsSelectedProperty, b);
}
}
}
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
public class Model : INotifyPropertyChanged
{
public string Name { get; set; }
private bool _Selected;
public bool Selected
{
get { return _Selected; }
set
{
if (value != _Selected)
{
_Selected = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I think the perfect way would be to implement the ListView.ItemSelectionChanged event, and iterate through each item in the list view model to set it to true for the item which is selected, false for the rest items.
However, You may want to try something like this, although honestly I'm not sure if this is the correct way:
class Model {
public string Name { get; set; }
public bool Selected
{
get
{
return MyListView.SelectedItems.Count(x => x.Name == Name) > 0;
}
}
}

BindableProperty not updated on ViewModel

In Xamarin.Forms I implemented a custom Picker.
The ItemsSource is set correctly. However when i change the selected item it does not update the property on my ViewModel.
The BindablePicker:
public class BindablePicker : Picker
{
public BindablePicker()
{
this.SelectedIndexChanged += OnSelectedIndexChanged;
}
public static BindableProperty ItemsSourceProperty =
BindableProperty.Create<BindablePicker, IEnumerable>(o => o.ItemsSource, default(IEnumerable), propertyChanged: OnItemsSourceChanged);
public static BindableProperty SelectedItemProperty =
BindableProperty.Create<BindablePicker, object>(o => o.SelectedItem, default(object), propertyChanged: OnSelectedItemChanged);
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private static void OnItemsSourceChanged(BindableObject bindable, IEnumerable oldvalue, IEnumerable newvalue)
{
var picker = bindable as BindablePicker;
picker.Items.Clear();
if (newvalue != null)
{
//now it works like "subscribe once" but you can improve
foreach (var item in newvalue)
{
picker.Items.Add(item.ToString());
}
}
}
private void OnSelectedIndexChanged(object sender, EventArgs eventArgs)
{
if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
{
SelectedItem = null;
}
else
{
SelectedItem = Items[SelectedIndex];
}
}
private static void OnSelectedItemChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var picker = bindable as BindablePicker;
if (newvalue != null)
{
picker.SelectedIndex = picker.Items.IndexOf(newvalue.ToString());
}
}
}
The Xamlpage:
<controls:BindablePicker Title="Category"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
Grid.Row="2"/>
The ViewModel properties, didn't implement the NotifyPropertyChanged on the properties since they only need to be updated from the ´Viewto theViewModel`:
public Category SelectedCategory { get; set; }
public ObservableCollection<Category> Categories { get; set; }
When creating your BindableProperty:
public static BindableProperty SelectedItemProperty =
BindableProperty.Create<BindablePicker, object>(o => o.SelectedItem, default(object), propertyChanged: OnSelectedItemChanged);
without specifying the defaultBindingMode, the BindingMode is set to OneWay, meaning the Binding is updated from source (your view model) to target (your view).
This can be fixed by changing the defaultBindingMode:
public static BindableProperty SelectedItemProperty =
BindableProperty.Create<BindablePicker, object>(o => o.SelectedItem, default(object), BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);
or, if it's the default you want for your picker, but want to update the source only in this view, you can specify the BindingMode for this instance of the Binding only:
<controls:BindablePicker Title="Category"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"
Grid.Row="2"/>
Beside adding the Mode=TwoWay to my binding a had to change some things in my picker so it could work with the actual objects i had it bound to.
The Items property of the Xamarin Picker is an IList<string>
since all my items are added to it as a string it keeps the same indexed value.
Therefor the ItemsSource is changed to an IList:
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
I also modified the SelectedIndexChangedmethod so it doesn't retrieve the item from the Items but from the ItemsSource, wich is in my case an IList<Category>:
private void OnSelectedIndexChanged(object sender, EventArgs eventArgs)
{
if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
{
SelectedItem = null;
}
else
{
SelectedItem = ItemsSource[SelectedIndex];
}
}
In my ViewModel i no longer use the ObservableCollection for my Categories but add these items to an IList<Category>.
The ObservableCollectionhas no use since when my BindablePicker binds to the ItemsSource the items are added to the internal IList<string>. when adding an item to the collection it will not be updated. I now update the entire ItemSourceif an item is changed.

How to show current date time in Textblock in TopAppBar-WinRT

i am trying to show the current sysdatetime in Top App bar and i was wondering anyway i can do that in XAML for win store apps.
public class MainViewModel : INotifyPropertyChanged
{
private readonly DispatcherTimer _timer = new DispatcherTimer();
private string _resDateTime;
public string ResDateTime
{
get
{
return _resDateTime;
}
set
{
_resDateTime = value;
NotifyPropertyChanged("ResDateTime");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
public MainViewModel()
{
_timer.Tick += TimerOnTick;
_timer.Start();
}
private void TimerOnTick(object sender, object o)
{
ResDateTime = DateTime.Now.ToString();
}
}
add to the code behind
public MainPage()
{
this.InitializeComponent();
DataContext = new MainViewModel();
}
and put on xaml
<Page.TopAppBar>
<AppBar>
<TextBlock Text="{Binding ResDateTime}"></TextBlock>
</AppBar>
</Page.TopAppBar>
hope it will help
In your page you can set Page.TopAppBar, and Page.BottomAppBar like this:
<Page.TopAppBar>
<AppBar>
<TextBlock Text="Your text" />
</AppBar>
</Page.TopAppBar>
From there, you can whether bind the Text property, if you're using the MVVM pattern, or simply assign a value in the code behind of the page, by giving a name to the TextBlock element.