ComboBox(Previous DataContext) SelectedItem property set as null value in UWP? - c#

Initially, ComboBox DataContext is set with Profession1 and SelectedValue as Politician. At Runtime, i changed the Datacontext to Profession2. Doing this is changing the Profession1 to null.
Please refer the below code:
<Page.Resources>
<local:MainPageViewModel x:Key="datacontent"></local:MainPageViewModel>
</Page.Resources>
<ComboBox x:Name="comboBox"
ItemsSource="{Binding Professions,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Profession, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Width="100"
Height="100"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
Code Behind:
var datacontent = (this.Resources["datacontent"] as MainPageViewModel);
this.comboBox.DataContext = datacontent.Profession1;
Model:
public class MainPageViewModel
{
public MainPageViewModel()
{
Profession1 = new Person();
Profession2 = new Person();
}
private Person profession1;
public Person Profession1
{
get { return profession1; }
set { this.profession1 = value; }
}
private Person profession2;
public Person Profession2
{
get { return profession2; }
set { this.profession2 = value; }
}
}
public class Person : INotifyPropertyChanged
{
public Person()
{
_professions = new List<string>();
_professions.Add("Lawyer");
_professions.Add("Politician");
_professions.Add("Other");
}
private string _profession;
public string Profession
{
get
{
if (string.IsNullOrWhiteSpace(_profession))
{
// _profession = _professions.LastOrDefault();
}
return _profession;
}
set
{
if (_profession != value)
{
_profession = value;
NotifyPropertyChanged("Profession");
}
}
}
private List<string> _professions;
public List<string> Professions
{
get
{
return _professions;
}
}
}
I have used the below code, to check the previous datacontext (Profession1->Professon) value .
Code
((this.Resources["datacontent"] as MainPageViewModel).Profession1 as Person).Profession
Output is : null.
Expected value : Politician
Please someone suggest on this.

((this.Resources["datacontent"] as MainPageViewModel).Profession1 as Person).Profession
Output is : null. Expected value : Politician
Please someone suggest on this.
The problem is that when you modify the DataContext of combobox, the DataContext is set null first and then turns to Profession2. So the Profession property of Profession1 will be set null. For your requirement, you could set the judgment condition to solve this issue.
public string Profession
{
get
{
return _profession;
}
set
{
if (_profession != value && value != null)
{
_profession = value;
OnPropertyChange();
}
}
}

Related

How to bind properties of class that might be null - sign of bad design

I am running into a scenario where I am binding to a property from my viewmodel in my xaml view where the VM property might be null. This causes my view not to load because I believe that I am getting a NullReferenceException.
VM:
public class PersonDetailViewModel : ViewModelBase
{
public Person CurrentPerson
{
get => currentPerson;
set => SetProperty(ref currentPerson, value);
}
private Person currentPerson;
public bool IsBobsFriendsVisible => FriendNamedBob?.Friends?.Count > 0;
public Person FriendNamedBob => CurrentPerson?.Friends?.FirstOrDefault(x => x.Name == "Bob");
public PersonDetailViewModel()
{
CurrentPerson = new Person()
{
Name = "Henry",
Friends = new List<Person>() { new Person() { Name = "Rachel" } }
};
}
}
XAML:
<ContentPage>
<ContantPage.Content>
<StackLayout>
<Label Text="Bob's Friends Count:" IsVisible="{Binding IsBobsFriendsVisible}" />
<Label Text="{Binding FriendNamedBob.Friends.Count}" IsVisible="{Binding IsBobsFriendsVisible}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
This line is causing the issue obviously since FriendNamedBob is null:
<Label Text="{Binding FriendNamedBob.Friends.Count}" IsVisible="{Binding IsBobsFriendsVisible}" />
What is the recommended technique for dealing with this scenario? Is this a sign of bad design?
Update:
It seems that a FallBackValue of sorts (TargetNullValue included) is not yet supported in Xamarin.Forms https://github.com/xamarin/Xamarin.Forms/issues/1803 also the DataTrigger does not work when checking for null https://bugzilla.xamarin.com/show_bug.cgi?id=57863
Thus, this is not a duplicate (yet) -- what are people currently doing in this situation?
Here let me explain to handle the Null binding in Xaml. And i used same format to bind the list of object in xaml and it's work.
Public Class Person
{
Public Person()
{
}
int id=0;--- Here we are doing default Initialization of object to handle default null values
public int ID
{
get
{
return id;
}
set
{
id = value;
OnPropertyChanged();
}
}
string name="";--- Here we are doing default Initialization of object to handle default null values
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged();
}
}
}
Now we can bind directly in XAML

How to resolve the error 'Property is null or is not IEnumerable' in Xamarin

I'm trying to making the following
<platform:TablePrac.Columns>
<platform:TextColumn Caption="it"/>
<platform:TextColumn Caption="is"/>
</platform:TablePrac.Columns>
But when I run this the error 'Property Columns is null or is not IEnumerable' is occurred.
The code flow is following.
When I wrote like above .xaml code, the property named Columns set the value.
(Columns is defined like below)
public List<Column> Columns
{
set
{
columns = value;
SetValue(ColumnsProperty, value);
}
get
{
return (List<Column>)GetValue(ColumnsProperty);
}
}
public class Column
{
public string caption;
public Type type;
}
public class TextColumn : Column
{
public TextColumn() : base()
{
this.type = typeof(string);
}
public TextColumn(string cap) : base()
{
this.caption = cap;
this.type = typeof(string);
}
public string Caption
{
set { caption = value; }
get { return caption; }
}
public Type Type
{
get { return type; }
}
}
As a very similar case, defining StackLayout and making new views in it like below
<StackLayout>
<Label Text="it"/>
<Label Text="is"/>
</StackLayout>
is same in .cs code like below
StackLayout stack = new StackLayout
{
Children =
{
new Label { Text = "it"},
new Label { Text = "is"}
}
};
So, I want to make property Columns work as StackLayout in .xaml but I don't know how. I spend two days to solve it.... I need your help
Thank you.
(Plus, StackLayout and Children are defined like below
StackLayout
public class StackLayout : Layout<View>
Layout
[Xamarin.Forms.ContentProperty("Children")]
public abstract class Layout<T> : Layout, IViewContainer<T>
where T : View
{
public IList<T> Children { get; }
...
}
)
The problem is not IEnumerable but Null value.
When using BindableProperty in Xamarin.Forms, you can assign a default value to the Property. For example, give default value 'new List()' solve this problem.
Follwing is my code, if you have same problem, check it.
Before :
public static readonly BindableProperty ColumnsProperty = BindableProperty.Create("Columns", typeof(List<Column>), typeof(TablePrac));
public List<Column> Columns
{
set
{
SetValue(ColumnsProperty, value);
}
get
{
return (List<Column>)GetValue(ColumnsProperty);
}
}
After :
public static readonly BindableProperty ColumnsProperty = BindableProperty.Create("Columns", typeof(IEnumerable<Column>), typeof(TablePrac), new List<Column>());
public IEnumerable<Column> Columns
{
set
{
SetValue(ColumnsProperty, value);
}
get
{
return (IList<Column>)GetValue(ColumnsProperty);
}
}
I convert type of return value of Columns to IList since in case of 'StackLayout's Children', the type of Children is IList type. There is no other reason.

Multibinding XamDataGrid

I am trying to use the following code example from the Infragistics site and I'd like edits in the XamDataCards to be reflected in the XamDataGrid. However, my DataSource for the XamDataGrid is an ObservableCollection<Companies> in my ViewModel. How can I also bind to the card and relay updates back to my Companies object in the ViewModel?
<igDP:XamDataGrid x:Name="dgCompanies" Theme="IGTheme" DataSource="{Binding Companies}" SelectedDataItemsScope="RecordsOnly">
<igDP:XamDataGrid.FieldSettings>
<igDP:FieldSettings CellClickAction="SelectCell" AllowEdit="True"/>
</igDP:XamDataGrid.FieldSettings>
</igDP:XamDataGrid>
<igDP:XamDataCards x:Name="XamDataCards1"
Grid.Row="1"
DataSource="{Binding Path=SelectedDataItems, ElementName=dgCompanies}"
Theme="IGTheme">
Edit: Added ViewModel
public class CompanyMgmtViewModel : ViewModelBase
{
private ObservableCollection<Object> _Companies = null;
public ObservableCollection<Object> Companies
{
get { return _Companies; }
set
{
if (_Companies != value)
{
_Companies = value;
RaisePropertyChanged(GetPropertyName(() => Companies));
}
}
}
public CompanyMgmtViewModel()
{
this.LoadData();
}
public void LoadData()
{
ObservableCollection<Object> records = new ObservableCollection<Object>();
var results = from res in AODB.Context.TCompanies
select res;
foreach (var item in results)
if (item != null) records.Add(item);
Companies = records;
}
}
The Model/Context code is just EF Database First generated.
You would need to bind your XamDataGrid's SelectedDataItems property to a property of type object[] ie. SelectedCompanies in your ViewModel and bind to that for your XamDataCards' datasource.
The accepted answer in this thread has a sample that shows how to do this, albeit with a ListBox instead of XamDataCards:
http://www.infragistics.com/community/forums/t/89122.aspx
Just replace that ListBox with your XamDataCards control, it works and updates the XamDataGrid. The ViewModel in the example is contained in the MainWindow code-behind, so it is MVVM like you want.
more info:
http://help.infragistics.com/Help/Doc/WPF/2014.1/CLR4.0/html/xamDataGrid_Selected_Data_Items.html
IG's SelectedDataItems is an object[] :
http://help.infragistics.com/Help/Doc/WPF/2014.1/CLR4.0/html/InfragisticsWPF4.DataPresenter.v14.1~Infragistics.Windows.DataPresenter.DataPresenterBase~SelectedDataItems.html
I couldn't have gotten to this answer without Theodosius' and Ganesh's input - so thanks to them, they both had partial answers.
I first tried to bind the SelectedDataItems of the XamDataGrid to the XamDataCards by way of a property on the ViewModel as Theodosius suggested, but that wasn't enough. Thanks to Ganesh, I implemented INotifyPropertyChanged on my model objects, by inheriting from ObservableObject in MVVMLight (how did I not know the Model needed this?).
Below are the relevant pieces of code to make it work.
I also implemented PropertyChanged.Fody as documented here; that's where the TypedViewModelBase<T> and removal of RaisePropertyChanged() comes from.
I'm also creating my Model objects by using a LINQ/Automapper .Project().To<T>() call which can be found here.
Model
public class Company : ObservableObject
{
public Company() { }
public int id { get; set; }
public string strName { get; set; }
public string strDomicileCode { get; set; }
}
ViewModel
public class CompanyMgmtViewModel : TypedViewModelBase<Company>
{
private ObservableCollection<Object> _Companies = null;
private Object[] _selectedCompany = null;
public Object[] Company
{
get { return _selectedCompany; }
set
{
if (_Company != value)
{
_selectedCompany = value;
}
}
}
public ObservableCollection<Object> Companies
{
get { return _Companies; }
set
{
if (_Companies != value)
{
_Companies = value;
}
}
}
public CompanyMgmtViewModel()
{
this.LoadData();
}
public void LoadData()
{
ObservableCollection<Object> records = new ObservableCollection<Object>();
var results = AODB.Context.TCompanies.Project().To<Company>();
foreach (var item in results)
if (item != null) records.Add(item);
Companies = records;
}
}
View
<igDP:XamDataGrid x:Name="dgCompanies"
Theme="IGTheme"
DataSource="{Binding Companies, Mode=OneWay}"
SelectedDataItemsScope="RecordsOnly"
SelectedDataItems="{Binding Company}">
...
<igDP:XamDataCards x:Name="XamDataCards1"
Grid.Row="1"
DataSource="{Binding ElementName=dgCompanies, Path=SelectedDataItems}"
Theme="IGTheme">

DevExpress - MVVM - Generate TabItems with different ViewModels

i have a DXTabControl. The DXTabItems are generated via my ViewModel.
//MainViewModel
public MainViewModel()
{
var items = new ObservableCollection<DXTabItem>();
items.Add(
new DXTabItem()
{
Header = "Test1",
Content = new WebViewModel()
});
items.Add(
new DXTabItem()
{
Header = "Test2",
Content = new CMSViewModel()
});
TabItems = items;
}
private ObservableCollection<DXTabItem> _tabItems;
public ObservableCollection<DXTabItem> TabItems
{
get { return _tabItems; }
set { SetProperty(ref _tabItems, value, () => TabItems); }
}
I am working with a DataTemplate and my TabItem is still not showing any UserControl.
//MainView.xaml
<DataTemplate x:Key="WebTemplate" DataType="{x:Type viewmodel:WebViewModel}">
<view:WebView/>
</DataTemplate>
<DataTemplate x:Key="CMSTemplate" DataType="{x:Type viewmodel:CMSViewModel}">
<view:CMSView/>
</DataTemplate>
<datatemplate:TemplateSelector x:Key="DataTemplateSelector"
WebTemplate="{StaticResource WebTemplate}"
CMSTemplate="{StaticResource CMSTemplate}" />
<dx:DXTabControl ItemsSource="{Binding TabItems}" ItemTemplateSelector="{StaticResource DataTemplateSelector}" />
//DataTemplateSelector
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate WebTemplate { get; set; }
public DataTemplate CMSTemplate { get; set; }
public override DataTemplate SelectTemplate(Object item,
DependencyObject container)
{
if (item == null) return base.SelectTemplate(item, container);
if (item.GetType() == typeof(WebViewModel))
{
return WebTemplate;
}
else if (item.GetType() == typeof(CMSViewModel))
{
return CMSTemplate;
}
else return base.SelectTemplate(item, container);
}
}
Everything is working, except showing the content i need. No view is been shown. Any idea? Did i miss something?
The following answer is based on caliburn.micro.
Step 1: Add a convention to the bootstrapper
public Bootstrapper()
{
ConventionManager.AddElementConvention<DXTabControl>(DXTabControl.ItemsSourceProperty, "ItemsSource", "DataContextChanged")
.ApplyBinding = (viewModelType, path, property, element, convention) =>
{
if (!ConventionManager.SetBindingWithoutBindingOrValueOverwrite(viewModelType, path, property, element, convention, DXTabControl.ItemsSourceProperty))
{
return false;
}
var tabControl = (DXTabControl)element;
if (tabControl.ItemTemplate == null && tabControl.ItemTemplateSelector == null && property.PropertyType.IsGenericType)
{
var itemType = property.PropertyType.GetGenericArguments().First();
if (!itemType.IsValueType && !typeof(string).IsAssignableFrom(itemType))
{
tabControl.ItemTemplate = ConventionManager.DefaultItemTemplate;
}
}
ConventionManager.ConfigureSelectedItem(element, Selector.SelectedItemProperty, viewModelType, path);
if (string.IsNullOrEmpty(tabControl.DisplayMemberPath))
{
ConventionManager.ApplyHeaderTemplate(tabControl, DXTabControl.ItemHeaderTemplateProperty, DXTabControl.ItemHeaderTemplateSelectorProperty, viewModelType);
}
return true;
};
[...]
}
Now you can bind any Screen-Collection to your DXTabControl.
Step 2: Create a collection in the ViewModel
public class MainViewModel : Screen
{
public MainViewModel()
{
DisplayName = "DevExpress Test Environment";
}
private static BindableCollection<Screen> _tbCtrl = new BindableCollection<Screen>();
public BindableCollection<Screen> TbCtrl
{
get { return _tbCtrl; }
set
{
_tbCtrl = value;
NotifyOfPropertyChange(() => TbCtrl);
}
}
}
You can e.g. put any other ViewModel which is based on the Screen class to your collection. That means, you will be able to display your content for each tabitem.
Step 3: Create the DXTabControl in your View (XAML-Code)
<dx:DXTabControl x:Name="TbCtrl" />
Give it a go. Open for feedback.
/// Alternative solution without Caliburn.Micro
Step 1: Add the DXTabControl to your MainView (XAML-Code)
<dx:DXTabControl ItemsSource="{Binding TbCtrlItems}" />
Step 2: Your MainViewModel needs to add those items like i have described above (in my question), but in this case, you have to specify the content-property
public MainViewModel()
{
_tbCtrlItems.Add(new DXTabItem()
{
Header = "Test1",
Content = new Views.View1() {DataContext = new ViewModel1()}
});
_tbCtrlItems.Add(new DXTabItem()
{
Header = "Test2",
Content = new Views.View2() { DataContext = new ViewModel2() }
});
}
private ObservableCollection<DXTabItem> _tbCtrlItems = new ObservableCollection<DXTabItem>();
public ObservableCollection<DXTabItem> TbCtrlItems
{
get { return _tbCtrlItems; }
set { SetProperty(ref _tbCtrlItems, value, () => TbCtrlItems); }
}
I hope this answer is helpful.

wpf how to bind to List

i'm trying to bound a list of object to DataGrid but i'm getting wrong value:
the object class:
public class Attribute
{
public Attribute()
{
}
private string _name;
public string name
{
get { return _name; }
set { _name = value; }
}
private List<Value> _valueList = new List<Value>();
public List<Value> ValueList
{
get { return _valueList; }
set { _valueList = value; }
}
}
public class Value
{
private string _value;
public string value
{
get { return _value; }
set { _value = value; }
}
public override string ToString()
{
return _value.ToString();
}
}
and i'm having a list of objects: List<Attribute> attributes
attributeDataGrid.ItemsSource = attributes;
when i bound i get a grid with name column correct
but the "ValueList" shown as "(Collection)" instead of the string...
how should i bound the List ?
Your overriden ToString method in Value is not called, because WPF displays the content of ValueList in your second column, i.e. it displays ValueList.ToString().
What do you expect to see in the second column? A comma separated list of the values in the ValueList?
Try below things it works for me.
On Load Method write below code
Attribute atr = new Attribute();
atr.ValueList.Add(new Value() { value = "One" });
atr.ValueList.Add(new Value() { value = "Two" });
atr.ValueList.Add(new Value() { value = "Three" });
atr.ValueList.Add(new Value() { value = "Four" });
dataGrid.DataContext = atr.ValueList;
On XAML file try below
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False" ItemsSource="{Binding}" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding value}" />
</DataGrid.Columns>
</DataGrid>
Hope this code snippet helps to you.

Categories

Resources