Displaying member in comboboxcolumn only after clicking column - data-binding

I have a control, when I need dislay person with two column:
-fullname
-best friend
The problem is , that property BestFriend on Person is an object.
At start Person has his own BestFriend, but he can change it from combobox column.
Now, after control loaded the column with bestfriend is blank.
When I doubleclick at this column I can change bestfirend, and it sets bestfriend of this person.
But what I must to do to have at start not blank column?
I think, that the problem is, that control can't match bestfriend, with collection of bestfriend, so I think that I must match them by id, but I don't know how can I do ti.
<UserControl x:Class="MvvmLight1.MainPage"
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:telerik="http://schemas.telerik.com/2008/xaml/presentation" mc:Ignorable="d"
Height="300"
Width="300"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<telerik:RadGridView x:Name="grdSrL"
AutoGenerateColumns="False"
SelectionMode="Single"
IsReadOnly="False"
IsFilteringAllowed="True"
Height="386"
Width="460"
HorizontalAlignment="Left"
CanUserDeleteRows="False"
CanUserInsertRows="True"
CanUserReorderColumns="False"
CanUserResizeColumns="True"
ItemsSource="{Binding Persons}">
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn DataMemberBinding="{Binding FullName}" IsReadOnly="True" Header="FullName" />
<telerik:GridViewComboBoxColumn ItemsSource="{Binding Friends,Source={StaticResource Main}}" ItemsSourceBinding="{Binding Friends,Source={StaticResource Main}}" Header="1st"
DataMemberBinding="{Binding BestFriend}"
DisplayMemberPath="FullName" />
</telerik:RadGridView.Columns>
</telerik:RadGridView>
</Grid>
</UserControl>
the main model:
namespace MvvmLight1
{
public class Person:INotifyPropertyChanged
{
private string _fullName;
public string FullName
{
get { return _fullName; }
set
{
if (_fullName!=value)
{
_fullName = value;
OnPropertyChanged("FullName");
}
}
}
public int Id
{
get { return _id; }
set { _id = value; }
}
public Person BestFirend
{
get { return _bestFirend; }
set
{
if (_bestFirend!=value)
{
_bestFirend = value;
OnPropertyChanged("BestFirend");
}
}
}
private int _id;
private Person _bestFirend;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
and viewmodel:
using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;
namespace MvvmLight1.ViewModel
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
for (int i = 0; i < 3; i++)
{
var friend = new Person() {FullName = "Name" + (i + 3).ToString()};
_friends.Add(friend);
_persons.Add(new Person(){FullName = "Name"+i.ToString(),Id = i,BestFirend = friend});
}
}
private ObservableCollection<Person> _persons=new ObservableCollection<Person>();
public ObservableCollection<Person> Persons
{
get { return _persons; }
set
{
_persons = value;
}
}
public ObservableCollection<Person> Friends
{
get { return _friends; }
set
{
_friends = value;
}
}
private ObservableCollection<Person> _friends=new ObservableCollection<Person>();
}
}
and app xaml
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MvvmLight1.App"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MvvmLight1.ViewModel"
mc:Ignorable="d">
<Application.Resources>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
<vm:MainViewModel x:Key="Main"/>
</Application.Resources>
</Application>

Not an expert on GridViewComboBoxColumn, but could it be that it is looking an instance of an object in the bound list, and that instance is not in it?
With "normal" ComboBoxes you got the choice whether you use value binding or item binding. In case of itembindng, the ComboBox looks for the same instance in the list of values. If it cannot find it it does not select any item.
In case of Valuebinding, the SelectedValue is compared to the value specified by SelectedValuePath. This then means that there is no requirement that the list entry and the selected entry are the same instance.
But as I said, this is for box standard ComboBoxes, as for the Telerik controls ... I don't really know. But from my experience with them (with WebForm controls) they are a helpful bunch, if you ask questions in their user support forums.

Related

Xamarin Forms: How can i correctly bind data from two view models to a single view?

This is the short code for testing purpose. The problem is that the UI is not displaying the Text from the Label which is binded with ViewModelB. In debugging when I hover the mouse in xaml over the Text from the Label I see the right binding data is there, but the UI simply won't display. With ViewModelA there are no problems.
In XAML:
<StackLayout>
<StackLayout>
<StackLayout.BindingContext>
<testbinding:ViewModelA/>
</StackLayout.BindingContext>
<Button Command ="{Binding Get}"/>
</StackLayout>
<StackLayout>
<StackLayout.BindingContext>
<testbinding:ViewModelB/>
</StackLayout.BindingContext>
<Label Text="{Binding Metadata}"/>
</StackLayout>
ViewModelA: where BaseViewModel is a INotifyPropertyChanged interface
public ViewModelA:BaseViewModel
{
public ViewModelA()
{
Get = new Command(SendText);
vmB = new ViewModelB();
}
ViewModelB vmB;
public ICommand Get { get; }
private void SendText()
{
string data = "someText";
vmB.GetMetadata(data);
}
}
ViewModelB is like this:
class ViewModelB:BaseViewModel
{
private string _metadata = string.Empty;
public string Metadata
{
get { return _metadata; }
set
{
_metadata = value;
OnPropertyChanged();
}
}
GetMetadata()
{
Metadata = "Some text";
}
}
In ViewModelA there are more properties which I need and in ViewModelB is just one property which gets data from a function. I could make just one ViewModel from both of them which works fine, but I'm trying to keep them smaller and organized. I already tried so many scenarios and is getting really frustrating.
Thanks for helping.
In the second StackLayout in your xaml file you're not binding it's BindingContext property to the ViewModelB instance from ViewModelA but instead you are creating a new one.
Here's a working solution for you:
public class ViewModelA : BaseViewModel
{
public ViewModelB ViewModelB { get; }
public ICommand GetMetadataCommand { get; }
public ViewModelA()
{
ViewModelB = new ViewModelB();
GetMetadataCommand = new Command((_) => GetMetadata());
}
private void GetMetadata()
{
string data = "someText";
ViewModelB.GetMetadata(data);
}
}
public class ViewModelB : BaseViewModel
{
private string _metadata;
public string Metadata
{
get { return _metadata; }
set
{
_metadata = value;
OnPropertyChanged();
}
}
public void GetMetadata(string data)
{
Metadata = data;
}
}
XAMl:
<StackLayout>
<StackLayout x:Name="StackLayout1">
<StackLayout.BindingContext>
<local:ViewModelA />
</StackLayout.BindingContext>
<Button Command ="{Binding GetMetadataCommand}"/>
</StackLayout>
<StackLayout BindingContext="{Binding Source={x:Reference StackLayout1}, Path=BindingContext.ViewModelB}">
<Label Text="{Binding Metadata}" />
</StackLayout>
</StackLayout>

How to access the BindingContext of custom control in Xamarin.Forms

I have a CollectionView with ItemsSource set to ObservableCollection of type Employee.
The ItemTemplate of the CollectionView is a CustomControl that has 1 BindableProperty of Type Employee
MainPage.xaml:
<CollectionView ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}">
<CollectionView.ItemTemplate>
<DataTemplate>
<controls:CustomControl Employee="{Binding .}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
The CustomControl has an image (checked image to indicate selection).
CustomControl.xaml:
<Frame HasShadow="True"
BackgroundColor="Blue">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" />
<Image Source="check.png" />
</StackLayout>
</Frame>
CustomControl.xaml.cs:
public partial class CustomControl : ContentView
{
public CustomControl()
{
InitializeComponent();
}
public static BindableProperty EmployeeProperty = BindableProperty.Create(
propertyName: nameof(Employee),
returnType: typeof(Employee),
declaringType: typeof(CustomControl),
defaultValue: default(Employee),
defaultBindingMode: BindingMode.OneWay);
public Employee Employee
{
get
{
return (Employee)GetValue(EmployeeProperty);
}
set
{
SetValue(EmployeeProperty, value);
}
}
}
Model (Employee):
public class Employee: INotifyPropertyChanged
{
private int name;
public int Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private int isSelected;
public int IsSelected
{
get
{
return isSelected;
}
set
{
isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
#region PropertyChanged
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I am trying to create simple animation (FadeIn/FadeOut) for the checked image in the CustomControl so when an item is selected the image will fade in, and when unselected it will fade out. I could use IsVisible and set it to true/false but that's ugly.
My idea was to listen to PropertyChanged event of the Employee (which supposed to be the context of my CustomControl), and when the property IsSelected is modified, I will start the animation to show/hide the image. something like this
public CustomControl()
{
InitializeComponent();
(this.BindingContext as Employee).PropertyChanged += CustomControl_PropertyChanged;
}
private void CustomControl_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Employee.IsSelected))
{
//do animation to show/hide image
}
}
But couldn't access the Context of my CustomControl!
When I declare the binding in MainPage.xaml I am passing a single Emplyee objet as BindingContext (that dot, right?):
<controls:CustomControl Employee="{Binding .}" />
but after the CustomControl is initializd, the BindingContext is still null!
public CustomControl()
{
InitializeComponent();
var context = this.BindingContext; //this is null
}
How can I observe the changes on the IsSelected property of the Employee object from my CustomControl?
In your custom control override the OnBindingContextChanged method, inside of that method you should be able to access the binding context that is set for your view.
Ex:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
var context = this.BindingContext as Employee
}

Could not bind to nested BindableProperty

I have created bindable property called Text in TargetClass.cs. That Text property is nested bindable property.
TargetClass.cs :
public class TargetClass : BindableObject
{
public static readonly BindableProperty TextProperty =
BindableProperty.Create("Text", typeof(string), typeof(TargetClass), "Default", BindingMode.TwoWay, null,
OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
}
Then I have created MyView.cs
public class MyView : ContentView
{
private TargetClass target;
Label label;
public TargetClass Target
{
get
{
return target;
}
set
{
target = value;
label.Text = target.Text;
}
}
public MyView()
{
label = new Label();
label.FontSize = 50;
Content = label;
}
}
ViewModel.cs :
public class ViewModel : INotifyPropertyChanged
{
private string m_text = "New Value";
public string TextValue
{
get { return m_text; }
set
{
m_text = value;
OnPropertyChanged("TextValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage.xaml :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:BindingDemo"
x:Class="BindingDemo.MainPage">
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="{Binding TextValue}"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
It is working when I give some string value to Text property like,
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="Hello World"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
in xaml.
But it doesnt work in MVVM binding When I bind the Text property like,
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="{Binding TextValue}"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
There is no problem with MVVM binding(ViewModel.cs) because it works well with another bindable property.
But it works if the Text property is added in MyView.cs and used like MyView.Text. It doesn't work only when it is added in TargetClass.cs and used like MyView.TargetClass.Text.
Is it possible to use the nested property in data binding in Xamarin Forms?
I don't think objects in the resource dictionary get a BindingContext assigned to them.
So you should modify it from code-behind to assign a context to any resource of type BindableObject
in your page.axml.cs:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (this.Resources != null)
{
foreach (var resource in this.Resources.Values.OfType<BindableObject>())
{
resource.BindingContext = this.BindingContext;
}
}
}

ComboBox selectedItem affect another ComboBox

I have a question about Databinding which I'm really struggling to understand. I have two ComboBox on my extended splash screen. What I want to achieve is when you select an item from the first ComboBox, the items in the 2nd ComboBox should change. Please see the code below.
First let me try to explain how my Data looks like and what problem I'm facing.
Collection
A
AA
AB
AC
B
BA
BB
C
CA
The First ComboBox should show A,B and C as items. Now lets say you selected A, the ComboBox 2 should show AA,AB and AC as items. The problem I have is that ComboBox 2 is showing AA only not all 3 items.
My ViewModel Called MainViewModel looks like this:-
public class ItemViewModel : INotifyPropertyChanged
{
private string _befattning;
public string Befattning
{
get
{
return _befattning;
}
set
{
if (value != _befattning)
{
_befattning = value;
NotifyPropertyChanged("Befattning");
}
}
}
private string _befattning2;
public string Befattning2
{
get
{
return _befattning2;
}
set
{
if (value != _befattning2)
{
_befattning2 = value;
NotifyPropertyChanged("Befattning2");
}
}
}
private string _befattning3;
public string Befattning3
{
get
{
return _befattning3;
}
set
{
if (value != _befattning3)
{
_befattning3 = value;
NotifyPropertyChanged("Befattning3");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
//NotifyPropertyChanged Code
}
}
public class MainViewModelGroups : INotifyPropertyChanged
{
public MainViewModelGroups(String enhet)
{
this._enhetsNamn = enhet;
}
private string _enhetsNamn;
public string EnhetsNamn
{
get { return _enhetsNamn; }
}
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
if (value != _selectedItem)
{
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
}
private ObservableCollection<ItemViewModel> _items = new ObservableCollection<ItemViewModel>();
public ObservableCollection<ItemViewModel> Items
{
get
{
return this._items;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
//NotifyPropertyChangedCode
}
}
public sealed class MainViewModel
{
private static MainViewModel _mainViewModel = new MainViewModel();
private ObservableCollection<MainViewModelGroups> _collection = new ObservableCollection<MainViewModelGroups>();
public ObservableCollection<MainViewModelGroups> Collection
{
get { return this._collection; }
}
public MainViewModel()
{
var enhet1 = new MainViewModelGroups("Akutmottagning");
enhet1.Items.Add(new ItemViewModel() { Befattning = "Ledningsansvarig sjuksköterska" });
Collection.Add(enhet1);
}
And my XAML code looks like this
<ComboBox x:Name="EnhetLista"
ItemsSource="{Binding Collection}"
SelectedItem="{Binding SelectedItem, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding EnhetsNamn}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="BefattningsLista"
DataContext="{Binding ElementName=EnhetLista, Path=SelectedItem, Mode=OneWay}"
ItemsSource="{Binding Path=Items, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Can someone please helps by explaining what is wrong with my code and how to achieve my goal?
Add a second ObservableCollection to your viewmodel, then change that based on the selected item.
public sealed class MainViewModel
{
private static MainViewModel _mainViewModel = new MainViewModel();
private ObservableCollection<MainViewModelGroups> _collection = new ObservableCollection<MainViewModelGroups>();
public ObservableCollection<MainViewModelGroups> Collection
{
get { return this._collection; }
}
public ObservableCollection<ItemViewModel> Items
{
get { return this._items; }
set { this._items = value; OnPropertyChanged("Items"); }
}
public MainViewModelGroups SelectedGroup
{
get { return this._selectedGroup; }
set { this._selectedGroup = value; Items = value.Items; }
}
public MainViewModel()
{
var enhet1 = new MainViewModelGroups("Akutmottagning");
enhet1.Items.Add(new ItemViewModel() { Befattning = "Ledningsansvarig sjuksköterska" });
Collection.Add(enhet1);
}
}
Your Xaml will change to:
<ComboBox x:Name="EnhetLista"
ItemsSource="{Binding Collection}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding EnhetsNamn}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="BefattningsLista"
ItemsSource="{Binding Path=Items}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Your viewmodel may need to implement INotifyPropertyChanged. It may not though, as ObservableCollection may take care of that for you.

Listbox binding in Silverlight

I have a listbox on silverlight page, the datacontext of the page is set to an instance -- TestQuestions, please see the code below. The listbox uses a DataTemplate and its ItemSource is 'Answers' which is a property of the page's DataContext, everything works fine until I try to bind to 'ShowAnswer', a property on the page's DataContext within the DataTemplate. No matter what I tried it won't pick the property's value.
Thank you all for your help.
Xaml:
<ListBox ItemsSource="{Binding Path=Answers, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton IsChecked="{Binding Path=Correct, Mode=TwoWay}" >
<Grid>
<StackPanel Visibility="{Binding Path=ShowAnswer}">
<Rectangle Fill="Blue" Visibility="{Binding Path=Correct}" />
</StackPanel>
<TextBlock Text="{Binding Path=AnswerText, Mode=TwoWay}" TextWrapping="Wrap" />
</Grid>
</RadioButton>
</DataTemplate>
</ListBox.ItemTemplate>
C#:
public class Answer
{
private bool correct = false;
public bool Correct
{
get { return correct; }
set
{
correct = value;
NotifyPropertyChanged("Correct");
}
}
private string answerText = false;
public string AnswerText
{
get { return answerText; }
set
{
answerText = value;
NotifyPropertyChanged("AnswerText");
}
}
}
public class TestQuestions
{
private ObservableCollection<Answer> answers = new ObservableCollection<Answer>();
public ObservableCollection<Answer> Answers
{
get { return answers; }
set
{
if (answers != value)
{
answers = value;
NotifyPropertyChanged("Answers");
}
}
}
private bool showAnswer = false;
public bool ShowAnswer
{
get { return showAnswer; }
set
{
showAnswer = value;
NotifyPropertyChanged("ShowAnswer");
}
}
}
It looks to me like you're trying to bind the UIElement.Visibility to a boolean value. Change your 'ShowAnswer' property from a boolean to a Visibility property and you should be set.
private Visibility showAnswer = Visibility.Collapsed;
public Visibility ShowAnswer
{
get { return showAnswer; }
set
{
showAnswer = value;
NotifyPropertyChanged("ShowAnswer");
}
}
EDIT:
If you are trying to bind to a property on the DataContext of the parent control, you could do this:
Name your UserControl
Example:
<UserControl x:Class="MvvmLightProto.MainPage"
x:Name="mainProtoPage"
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"
mc:Ignorable="d"
Height="700"
Width="1200">
In the above example, the UserControl has the name of "mainProtoPage", now in your XAML, you could do this:
<StackPanel Visibility="{Binding DataContext.ShowAnswer, ElementName=mainProtoPage}">

Resources