XAML nested template binding - c#

I have a list view where I bind multiple data templates to using a DataTemplateSelector. However, I cannot build my project, I'm getting an error of "Object reference not set to an instance of an object". I narrowed down the issue to the data binding ItemsSource of the combobox. If I comment out the combobox, my project will build. As I understand, the Binding keyword evaluates at runtime, but this is a compile time error. I tried to get around the error by swapping out to x:Bind, as that evaluates at compile-time but then that required the type of the DataTemplate to be defined.
x:DataType={x:Type templates:FilterDropdownDataTemplate}
However, x:Type isn't defined in a Windows 10 universal app. Any suggestions?
Here is my XAML:
<Page.Resources>
<DataTemplate x:Name="DateComboboxTemplate">
<TextBlock Text="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="FilterDropdownDataTemplate">
<StackPanel>
<TextBlock Text="{Binding Value}"/>
<ComboBox ItemsSource="{Binding DateRanges}"
SelectionChanged="{Binding DateSelection}"
SelectedItem="{Binding SelectedDateRange, Mode=TwoWay}"
ItemTemplate="{StaticResource DateComboboxTemplate}">
</ComboBox>
</StackPanel>
</DataTemplate>
<config:MainFilterTemplateSelector
x:Name="MainFilterSelector"
FilterDropdownTemplate="{StaticResource FilterDropdownDataTemplate}"
/>
</Page.Resources>
<ListView x:Name="FilterCategories"
Margin="0"
Padding="0"
BorderThickness="0"
ItemsSource="{Binding Filters}"
SelectedItem="{Binding SelectedFilterCategory, Mode=TwoWay}"
IsItemClickEnabled="True"
SelectionChanged="{x:Bind ViewModel.OpenSubFilters}"
ItemTemplateSelector="{StaticResource MainFilterSelector}">
</ListView>
And here is the relevant C# code (I'm using a MVVM pattern):
public class MainFilterViewDropdownListTemplate : Filter
{
private ObservableCollection<string> _dateRanges;
private string _selectedDateRange;
public MainFilterViewDropdownListTemplate() : base()
{
_dateRanges = new ObservableCollection<string>();
}
public ObservableCollection<string> DateRanges
{
get
{
return _dateRanges;
}
set
{
SetProperty(ref _dateRanges, value);
// hard coding these 4 lines for testing for now
_dateRanges.Add("Last 7 days");
_dateRanges.Add("Last 14 days");
_dateRanges.Add("Last 30 days");
_dateRanges.Add("Custom");
}
}
public string SelectedDateRange
{
get
{
return _selectedDateRange;
}
set
{
SetProperty(ref _selectedDateRange, value);
}
}
public IEnumerable<Filter> AllFilters { get; set; }
public void InitializeFields(Filter filter)
{
Column = filter.Column;
DisplayType = filter.DisplayType;
ID = filter.ID;
IsSelected = filter.IsSelected;
ParentId = filter.ParentId;
SelectedCount = filter.SelectedCount;
TotalCount = filter.TotalCount;
Value = filter.Value;
visibility = filter.visibility;
}
private void DateSelection()
{
}
}
And
public class MainFilterTemplateSelector : DataTemplateSelector
{
public DataTemplate FilterDropdownTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object selector)
{
if (selector == null)
{
throw new ArgumentNullException("Template is null");
}
if (!(selector is FilterBase))
{
throw new ArgumentException("This list can only be populated by a class that extends Filter.");
}
else
{
var filterType = selector as Filter;
switch (filterType.DisplayType)
{
case "date":
return FilterDropdownTemplate;
default:
return base.SelectTemplateCore(selector);
}
}
}
}
And
public class Filter : FilterBase
{
private int _selectedCount;
private int _totalCount;
private Visibility _visibility;
public IEnumerable<Filter> Children
{
get;
set;
}
public int TotalCount
{
get
{
return _totalCount;
}
set
{
SetProperty(ref _totalCount, value);
}
}
public int SelectedCount
{
get
{
return _selectedCount;
}
set
{
SetProperty(ref _selectedCount, value);
}
}
public Visibility visibility
{
get
{
return _visibility;
}
set
{
SetProperty(ref _visibility, value);
}
}
public void InitializeAllChildren(IEnumerable<Filter> allFilters)
{
foreach (Filter item in allFilters)
{
item.Children = allFilters.GetAllChildrenFiltersForParentID(item.ID) as IEnumerable<Filter>;
if (item.Children == null)
{
item.TotalCount = 0;
}
else
{
item.TotalCount = item.Children.Count();
item.SelectedCount = item.Children.Where(t => t.IsSelected.Value).Count();
}
}
Children = allFilters.GetAllChildrenFiltersForParentID(ID);
}
}
And
public class FilterBase : ViewModelBase
{
public int ID
{
get
{
return _id;
}
set
{
SetProperty(ref _id, value);
}
}
public string Value
{
get
{
return _value;
}
set
{
SetProperty(ref _value, value);
}
}
public string Column
{
get
{
return _column;
}
set
{
SetProperty(ref _column, value);
}
}
public int ParentId
{
get
{
return _parentId;
}
set
{
SetProperty(ref _parentId, value);
}
}
public string DisplayType
{
get
{
return _displayType;
}
set
{
SetProperty(ref _displayType, value);
}
}
public bool? IsSelected
{
get
{
return _isSelected;
}
set
{
SetProperty(ref _isSelected, value);
}
}
private int _id;
private string _value;
private string _column;
private int _parentId;
private string _displayType;
private bool? _isSelected;
}
And (BindableBase is from the Prism.Mvvm namespace)
public class ViewModelBase : BindableBase, IDisposable
{
PropertyChangeActionHelper _propertyChangeActionHelper;
protected void RegisterPropertyChangeAction(Action action, params string[] monitoredProperties)
{
if (_propertyChangeActionHelper == null)
{
_propertyChangeActionHelper = new PropertyChangeActionHelper(this);
}
_propertyChangeActionHelper.RegisterPropertyChangeAction(action, monitoredProperties);
}
#region IDisposable Support
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_propertyChangeActionHelper != null)
{
_propertyChangeActionHelper.Dispose();
_propertyChangeActionHelper = null;
}
}
}
~ViewModelBase()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}

Assuming that the templates namespace is declared and FilterDropdownDataTemplate is a type within that namespace, you should be able to define the DataTemplate's data type without x:Type using the following syntax:
<DataTemplate x:DataType="templates:FilterDropdownDataTemplate">

Related

IDataErrorInfo Issue - Initial property values

i have a ViewModel that implements the IDataErrorInfo interface as usual.
Model
public class Computer : Model
{
public Computer(string name, string ip, string mac, string broadcastIp)
{
Name = name;
Ip = ip;
Mac = mac;
BroadcastIp = broadcastIp;
}
public Computer()
{
Name = "neuer Computer";
Mac = string.Empty;
Ip = string.Empty;
BroadcastIp = string.Empty;
}
[NotMapped]
public ComputerState ComputerState { get; set; }
public string Name { get; set; }
public string Ip { get; set; }
public string Mac { get; set; }
public string BroadcastIp { get; set; }
public virtual Room Room { get; set; }
}
ViewModel
public class ComputerViewModel : ViewModel<Computer>, IListItem
{
private bool _IsSelected;
private ComputerOperationMessage _OperationMessage = ComputerOperationMessage.NO_MESSAGE;
public ComputerViewModel(Computer computer) : base(computer)
{
}
public string Name
{
get { return Model.Name; }
set
{
Model.Name = value;
RaisePropertyChanged(nameof(Name));
}
}
public RoomViewModel Room
{
get { return App.EntityManager.Get<RoomViewModel>().FirstOrDefault(r => r.Model.Equals(Model.Room)); }
set
{
Model.Room = value.Model;
RaisePropertyChanged(nameof(Room));
}
}
public ComputerState State
{
get { return Model.ComputerState; }
set
{
if (value.Equals(Model.ComputerState))
return;
Model.ComputerState = value;
RaisePropertyChanged(nameof(State));
}
}
public string Ip
{
get { return Model.Ip; }
set
{
Model.Ip = value;
RaisePropertyChanged(nameof(Ip));
}
}
public string Mac
{
get { return Model.Mac; }
set
{
Model.Mac = value;
RaisePropertyChanged(nameof(Mac));
}
}
public string BroadcastIp
{
get { return Model.BroadcastIp; }
set
{
Model.BroadcastIp = value;
RaisePropertyChanged(nameof(BroadcastIp));
}
}
/// <summary>
/// UI indicator when a operation like shutdown, reboot, ping etc. is running
/// </summary>
public ComputerOperationMessage OperationMessage
{
get { return _OperationMessage; }
set
{
if (value.Equals(_OperationMessage))
return;
_OperationMessage = value;
RaisePropertyChanged(nameof(OperationMessage));
}
}
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
public override string ToString()
{
return Name;
}
protected override string ValidateProperty(string property)
{
var computers = App.EntityManager.Get<ComputerViewModel>().ToList();
switch (property)
{
case nameof(Name):
if (string.IsNullOrEmpty(Name))
{
return "Bezeichnung erwartet";
}
if (computers.Any(c => c.Name == Name))
{
return "Bezeichnung bereits vergeben";
}
break;
case nameof(Ip):
if (string.IsNullOrEmpty(Ip))
{
return "Ip erwartet";
}
if (!NetworkCommand.ValidateIp(Ip))
{
return "Ip ungültig";
}
if (computers.Any(c => c.Ip == Ip))
{
return "Ip bereits vergeben";
}
break;
case nameof(Mac):
if (string.IsNullOrEmpty(Mac))
{
return "Mac erwartet";
}
if (!NetworkCommand.ValidateMac(Mac))
{
return "Mac ungültig";
}
if (computers.Any(c => c.Mac == Mac))
{
return "Mac bereits vergeben";
}
break;
case nameof(BroadcastIp):
if (string.IsNullOrEmpty(BroadcastIp))
{
return "Broadcast Ip erwartet";
}
if (!NetworkCommand.ValidateIp(Ip))
{
return "Broadcast Ip ungültig";
}
break;
case nameof(Room):
if (Room == null)
return "Raum wählen";
break;
}
return string.Empty;
}
public abstract class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string _ErrorMessage;
private bool _HasErrors;
public ViewModel()
{
SaveCommand = new RelayCommand(SaveAction);
}
public bool HasErrors
{
get { return _HasErrors; }
set
{
_HasErrors = value;
RaisePropertyChanged(nameof(HasErrors));
}
}
public ICommand SaveCommand { get; }
public Model Model { get; protected set; }
public string this[string property]
{
get
{
_ErrorMessage = ValidateProperty(property);
RaisePropertyChanged(nameof(Error));
HasErrors = !string.IsNullOrEmpty(Error);
return Error;
}
}
public string Error
{
get { return _ErrorMessage; }
}
public event PropertyChangedEventHandler PropertyChanged;
private void SaveAction(object obj)
{
if (Model != null && Model.Id == 0)
{
App.EntityManager.Add(Model);
}
App.EntityManager.Save();
}
protected virtual string ValidateProperty(string property)
{
return string.Empty;
}
protected void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public abstract class ViewModel<T> : ViewModel where T : Model
{
protected ViewModel(T model)
{
Model = model;
base.Model = model;
}
public new T Model { get; }
public long Id => Model.Id;
}
XAML:
<Label Content="{StaticResource NameString}" />
<TextBox Text="{Binding Path=Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="{StaticResource IpString}" />
<TextBox Text="{Binding Path=Ip,ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="{StaticResource MacString}" />
<TextBox Text="{Binding Path=Mac, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="{StaticResource BroadcastIpString}" />
<TextBox Text="{Binding Path=BroadcastIp, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="{StaticResource RoomString}" />
<ComboBox ItemsSource="{Binding Path=Rooms}" SelectedItem="{Binding Path=Room, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<GridSplitter Height="25" />
<Button x:Name="SaveButton"
HorizontalAlignment="Right"
Command="{Binding Path=SaveCommand}"
Content="{StaticResource SaveIcon}"
IsEnabled="{Binding Path=CanSave,
Converter={StaticResource TrueToFalseConverter},
UpdateSourceTrigger=PropertyChanged}" />
Calling code in class that implements ICommand interface:
var viewModel = parameter as ComputerViewModel;
_Editor = new ComputerEditor();
_Editor.CloseButtonCommand = new RelayCommand(CloseCommand);
_Editor.SaveButton.Click += (s, e) =>
{
App.EntityManager.Add(_Editor.ComputerEditViewModel.Model);
App.EntityManager.Save();
_Editor.IsOpen = false;
(Application.Current.MainWindow.Content as Grid).Effect = null;
};
if (viewModel == null)
{
_Editor.DataContext = new ComputerEditViewModel(new Computer());
_Editor.Title = Application.Current.FindResource("ComputerAddString").ToString();
}
else
{
_Editor.DataContext = new ComputerEditViewModel(viewModel.Model);
_Editor.Title = Application.Current.FindResource("ComputerEditString").ToString();
}
OpenChildWindow(_Editor);
My problem:
When i create a new entity without any initial values the IDataErrorInfo is executed all the time but the error (red border and label) is only shown when the new entity properties has valid initial values.
Control images
The (1) is the initial controlstate. The first textbox ('Name') has a default value that comes from the entity contructor. All other values (Textboxes) are just empty.
At this point my validation is working in my viewmodel for all textboxes/properties but only the first textbox get the red border and label when the validation fails, cause of the valid start value!
For the other properties i have to set a valid (initial) value before the red border and label is shown (when validation fails, cause of property change) although i check if the textbox/property is empty.
As i said, the validation is working in the viewmodel all the time but the view only shows the red border and label when the initial value is valid against my implementation.
Hoping this question is not to weird.
Feel free to ask anything if something is just unclear
Best regards
Dustin

Create TreeView with different child object in every node with full respect of WPF MVVM

I want to create treeView as following with full respect of Wpf MVVM:
Root //Level 0
B1- Child1 // Level 1
B1-1- Child1-1 // Level 2
B1-2- Child1-2
B1-3- ...
B2- Child2 // Level1
B2-1 Child2-1 // Level 2
B2-2 Child2-2
B2-3 ...
As you can see, I have TreeView with 3 Level.
Level 0: Root (Always Fix)
Level 1: Two child (Always Fix too)
Level 2: Dynamic Child and they are created from two different class
So My question is how Can I create different child in Level 2 for every node in Level 1.
I used the code below but I have always the same children under parents in Level 1.
I have looked at many of the solution proposed on this site as well as on the web... but just cant figure out how to do it...
My attempt:
public class MyViewModel
{
//private ReadOnlyCollection<AttributesMapDocViewModel> _attributeMapDoc;
public object _document;
#region Methodes
private List<Level0ViewModel> _myDoc;
public List<Level0ViewModel> MyDoc
{
get { return _myDoc; }
set
{
_myDoc = value;
}
}
#endregion
#region Constructeur
public MyViewModel()
{
MyDoc = new List<Level0ViewModel>()
{
new Level0ViewModel("Root",_document), //_document conatins data from xml file (code not shown)
};
}
#endregion
}
public class Level0ViewModel : ViewModelBase
{
private List<Level1ViewModel> _childLevel1;
public Level0ViewModel(string name, object myObj)
{
ChildLeve0Name = name;
ChildLevel1 = new List<Level1ViewModel>()
{
new Level1ViewModel("Child1",myObj),
new Level1ViewModel("Child2",myObj)
};
}
public List<Level1ViewModel> ChildLevel1
{
get
{
return _childLevel1;
}
set
{
SetProperty(ref _childLevel1, value, () => ChildLevel1);
}
}
public string ChildLeve0Name { get; set; }
}
public class Level1ViewModel : ViewModelBase
{
private ObservableCollection<Level2SecondTypeViewModel> _childLevel2SecondType;
public ObservableCollection<Level2SecondTypeViewModel> ChildLevel2SecondType
{
get { return _childLevel2SecondType; }
set
{
if (_childLevel2SecondType != value)
{
SetProperty(ref _childLevel2SecondType, value, () => ChildLevel2SecondType);
}
}
}
private ObservableCollection<Level2FirstTypeViewModel> _childLevel2FirstType;
public ObservableCollection<Level2FirstTypeViewModel> ChildLevel2FirstType
{
get { return _childLevel2FirstType; }
set
{
if (_childLevel2FirstType != value)
{
SetProperty(ref _childLevel2FirstType, value, () => ChildLevel2FirstType);
}
}
}
public Level1ViewModel(string name, object mapAtt)
{
ChildLevel1Name = name;
ChildLevel2FirstType = new ObservableCollection<Level2FirstTypeViewModel>();
foreach (FirstType myFirstType in mapAtt.FirstTypes)
{
ChildLevel2FirstType.Add(new Level2FirstTypeViewModel(myFirstType));
}
ChildLevel2SecondType = new ObservableCollection<Level2SecondTypeViewModel>();
foreach (SecondType mySecondType in mapAtt.SecondTypes)
{
ChildLevel2SecondType.Add(new Level2SecondTypeViewModel(mySecondType));
}
}
public string ChildLevel1Name
{
get;
set;
}
}
public class Level2FirstTypeViewModel : ViewModelBase
{
public Level2FirstTypeViewModel(FirstType fType)
{
FirstTypeName = fType.name;
}
public string FirstTypeName
{
get;
set;
}
}
public class Level2SecondTypeViewModel
{
public Level2SecondTypeViewModel(SecondType sType)
{
SecondTypeName = sType.name;
}
public string SecondTypeName
{
get;
set;
}
}
<TreeView ItemsSource="{Binding MyDoc}" >
<TreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding MapAttDef}" DataType="{x:Type local:Level0ViewModel}">
<Label Content="{Binding ChildLeve0Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildLevel2SecondType}" DataType="{x:Type local:Level1ViewModel}">
<Label Content="{Binding ChildLevel1Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Level2SecondTypeViewModel}">
<Label Content="{Binding FirstTypeName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Level2FirstTypeViewModel}">
<Label Content="{Binding SecondTypeName}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
My Attempt give me something like this ( that isn't what I want at all !!):
Root //Level 0
B1- Child1 // Level 1
B1-1- Child1-1 // Level 2
B1-2- Child1-2
B1-3- ...
B2- Child2 // Level1
B2-1 Child1-1 // Level 2
B2-2 Child1-2
B2-3 ...
Throw away these properties: Level1ViewModel.ChildLevel2FirstType and Level1ViewModel.ChildLevel2SecondType.
You need single collection for children. The item type of this collection depends on inheritance hierarchy and should be nearest common ancestor (worst case is object), that is, like this:
public Level1ViewModel
{
public ObservableCollection<object> ChildLevel2
{
// ...
}
// ...
}
Markup with these changes will look like this:
<!-- Level 1 -->
<HierarchicalDataTemplate DataType="{x:Type local:Level1ViewModel}" ItemsSource="{Binding ChildLevel2}">
<Label Content="{Binding ChildLevel1Name}"/>
</HierarchicalDataTemplate>
<!-- Level 1 -->
<DataTemplate DataType="{x:Type local:Level2FirstTypeViewModel}">
<Label Content="{Binding SecondTypeName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Level2SecondTypeViewModel}">
<Label Content="{Binding FirstTypeName}"/>
</DataTemplate>
Note, that HierarchicalDataTemplate must be used for hierarchical data sources only. For non-hierarchical ones (like Level2FirstTypeViewModel and
Level2SecondTypeViewModel) use regular DataTemplate.
public class BaseTreeViewItem<TSource, TParent> : ViewModelBase
{
// add static field to hold selected Item
public static BaseTreeViewItem<TSource, TParent> SelectedChild { get; set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected == value) return;
if (SelectedChild != null)
{
SelectedChild.IsSelected = false;
SelectedChild = null;
}
_isSelected = value;
if (_isSelected)
SelectedChild = this;
// NotifyPropertyChanged
}
}
public TParent Parent
{
get;
private set;
}
public BaseTreeViewItem(string name, TSource myObj, TParent parent)
{
ChildName = name;
DataSource = myObj;
Parent = parent;
}
public string ChildName { get; set; }
public TSource DataSource { get; set; }
public override string ToString()
{
return ChildName;
}
}
// Node
public class MyTreeViewItem<TChild, TSource, TParent> : BaseTreeViewItem<TSource, TParent>, IDeleteItem
where TParent : IDeleteItem
where TChild : class
{
public ObservableCollection<TChild> Children { get; set; }
public MyTreeViewItem(string name, TSource myObj, TParent parent)
:base(name,myObj, parent)
{
Children = new ObservableCollection<TChild>();
}
protected virtual void InitChild()
{
}
public void DeleteItem(object myTreeViewItem)
{
Children.Remove(myTreeViewItem as TChild);
}
public static void DeleteSelectedItem()
{
if (SelectedChild != null && SelectedChild.Parent != null)
{
SelectedChild.Parent.DeleteItem(SelectedChild);
}
}
}
public class Level0ViewModel : MyTreeViewItem<Level1ViewModel, XmlDocument, IDeleteItem>
{
protected override sealed void InitChild()
{
base.InitChild();
Children.Add(new Level1ViewModel("Child1", new Level1Src(), this));
Children.Add(new Level1ViewModel("Child2", new Level1Src(), this));
}
public Level0ViewModel(string name, XmlDocument myObj) :
base(name, myObj,null)
{
InitChild();
}
}
public class Level1ViewModel : MyTreeViewItem<Level2TypeViewModel, Level1Src, Level0ViewModel>
{
public Level1ViewModel(string name, Level1Src myObj, Level0ViewModel parent)
: base(name, myObj, parent)
{
InitChild();
}
protected override sealed void InitChild()
{
base.InitChild();
foreach (FirstType myFirstType in DataSource.FirstTypes)
{
Children.Add(new Level2TypeViewModel(myFirstType, this));
}
foreach (SecondType mySecondType in DataSource.SecondTypes)
{
Children.Add(new Level2TypeViewModel(mySecondType, this));
}
}
// Use linq if tou want child by type
public IEnumerable<Level2TypeViewModel> ChildType1
{
get
{
return Children.Where(item => item.DataSource is FirstType);
}
}
public IEnumerable<Level2TypeViewModel> ChildType2
{
get
{
return Children.Where(item => item.DataSource is SecondType);
}
}
}
public class LevelType
{
public string Name;
}
public class FirstType : LevelType
{
}
public class SecondType : LevelType
{
}
public class Level2TypeViewModel : BaseTreeViewItem<LevelType, Level1ViewModel>
{
public Level2TypeViewModel(LevelType sType, Level1ViewModel parent)
: base(sType.Name, sType, parent)
{
}
}
My Data Source is
public Level0ViewModel TreeModel
{
get
{
return new Level0ViewModel("Root", new XmlDocument());
}
}
For TreeView DataTemplate use
<TreeView DataContext="{Binding ElementName=UI, Path=TreeModel}">
<TreeView.Items>
<TreeViewItem Header="{Binding ChildName}" ItemsSource="{Binding Path=Children}" >
<TreeViewItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Label Content="{Binding ChildName}"></Label>
</HierarchicalDataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</TreeView.Items>
</TreeView>
The First treeviewItem is to show root node. if you do not want to use it
You can change data source to
public MyTreeViewItem<Level0ViewModel,object> TreeModel
{
get
{
MyTreeViewItem<Level0ViewModel,object> src = new MyTreeViewItem<Level0ViewModel, object>("Root", null);
src.Children.Add(new Level0ViewModel("toto", new XmlDocument()));
return src;
}
}
And DataTemplate to
<TreeView DataContext="{Binding ElementName=UI, Path=TreeModel}" ItemsSource="{Binding Children}">
<TreeViewItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Label Content="{Binding ChildName}"></Label>
</HierarchicalDataTemplate>
</TreeViewItem.ItemTemplate>
</TreeView>
sample way
public class TreeViewItemViewModel : ViewModelBase
{
// add static field to hold selected Item
public static TreeViewItemViewModel SelectedChild { get; set; }
public TreeViewItemViewModel Parent{ get; private set;}
public string ChildName { get; set; }
public object DataSource { get; set; }
private readonly ObservableCollection<TreeViewItemViewModel> _children;
public ObservableCollection<TreeViewItemViewModel> Children
{
get
{
return _children;
}
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected == value) return;
if (SelectedChild != null)
{
SelectedChild.IsSelected = false;
SelectedChild = null;
}
_isSelected = value;
if (_isSelected)
SelectedChild = this;
// NotifyPropertyChanged
}
}
public TreeViewItemViewModel(string name, object myObj, TreeViewItemViewModel parent)
{
ChildName = name;
DataSource = myObj;
Parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
}
public override string ToString()
{
return ChildName;
}
protected virtual void InitChild()
{
}
public void DeleteItem(TreeViewItemViewModel myTreeViewItem)
{
Children.Remove(myTreeViewItem);
}
public static void DeleteSelectedItem()
{
if (SelectedChild != null && SelectedChild.Parent != null)
{
SelectedChild.Parent.DeleteItem(SelectedChild);
}
}
}
If u have one selected Item in your view model u can use this approach
public class TreeViewItemViewModel : ViewModelBase
{
// add static field to hold selected Item
public static TreeViewItemViewModel SelectedChild { get; set; }
public TreeViewItemViewModel Parent{ get; private set;}
public string ChildName { get; set; }
public object DataSource { get; set; }
private readonly ObservableCollection<TreeViewItemViewModel> _children;
public ObservableCollection<TreeViewItemViewModel> Children
{
get
{
return _children;
}
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected == value) return;
if (SelectedChild != null)
{
SelectedChild.IsSelected = false;
SelectedChild = null;
}
_isSelected = value;
if (_isSelected)
SelectedChild = this;
// NotifyPropertyChanged
}
}
public TreeViewItemViewModel(string name, object myObj, TreeViewItemViewModel parent)
{
ChildName = name;
DataSource = myObj;
Parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
}
public override string ToString()
{
return ChildName;
}
protected virtual void InitChild()
{
}
public void DeleteItem(TreeViewItemViewModel myTreeViewItem)
{
Children.Remove(myTreeViewItem);
}
public static void DeleteSelectedItem()
{
if (SelectedChild != null && SelectedChild.Parent != null)
{
SelectedChild.Parent.DeleteItem(SelectedChild);
}
}
}

Binding To Navigation property doesn't refresh UI

I have this entity (BaseModel Implements INotifypropertyChanged):
EDIT 1: SetProperty code:
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
public partial class Movimientos : BaseModel
{
public Movimientos()
{
}
private Nullable<int> _intIdBodega;
public Nullable<int> IntIdBodega
{
get { return _intIdBodega; }
set { SetProperty(ref _intIdBodega, value); }
}
public virtual INV_Bodegas INV_Bodegas { get; set; }
}
With this navigation property:
/// Nav property
public partial class INV_Bodegas : BaseModel
{
public INV_Bodegas()
{
}
private int _intIdBodega;
public int IntIdBodega
{
get { return _intIdBodega; }
set { SetProperty(ref _intIdBodega, value); }
}
private string _strCodigoBodega;
public string StrCodigoBodega
{
get { return _strCodigoBodega; }
set { SetProperty(ref _strCodigoBodega, value); }
}
}
If user changes IntIdBodega property at the View model
public class VieModel
{
ObservableCollection<Movimientos> mov {get;set;}
public VieModel()
{
mov = new ObservableCollection<Movimientos>();
mov.CollecTionChanged += mov_CollectionChanged;
}
void mov_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
((Movimientos)item).PropertyChanged += det_PropertyChanged;
}
}
}
In the view model I update the navigation property, But Datagrid never update the values Binded to this property.
public void det_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Movimientos Idet = (Movimientos)sender;
// Here I Update The navigation property.
// But DataGrid never refresh the changes
if (e.PorpertyName == "IntIdBodega")
{
Idet.INV_Bodegas = db.INV_Bodegas
.Where(x=> x.IntIdbodega == Idet.Intidbodega)
.FirstOrDefault();
}
}
}
This is how I bind.
<DataGridTextColumn Header="{x:Static resources:Labels.Bodega}"
Width="0.5*"
Binding="{Binding Inv_Bodegas.StrCodigoBodega,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
IsReadOnly="True">
My problem is that this column never refresh If i change IsReadOnly to false, the changes can be reflected only if I press F2 at this column.
My question is:
How to force the UI to refresh this navigation properties ?
You have implemented the interface, but never raise the event. Try:
public string StrCodigoBodega
{
get { return _strCodigoBodega; }
set {
SetProperty(ref _strCodigoBodega, value);
this.PropertyChanged(this, new PropertyChangedEventArgs("StrCodigoBodega"));
}
}
See the example:
https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged%28v=vs.100%29.aspx

AutoSuggestBox not showing results

I'm having trouble displaying the results in the AutoSuggestBox on Windows Phone 8.1. I'm using MVVM Light to bind my itemsource to the Autosuggestbox.
<AutoSuggestBox Header="Van" Text="{Binding SearchTextFrom, Mode=TwoWay}" ItemsSource="{Binding suggestionFrom}">
<AutoSuggestBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding description}"/>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="TextChanged">
<core:InvokeCommandAction Command="{Binding SearchChangedFrom}">
</core:InvokeCommandAction>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</AutoSuggestBox>
My ViewModel
private RelayCommand _SearchChangedFrom;
public RelayCommand SearchChangedFrom
{
get
{
return _SearchChangedFrom ?? (_SearchChangedFrom = new RelayCommand(
async () =>
{
if (string.IsNullOrWhiteSpace(user.countrycode))
{
Debug.WriteLine("Could not autocomplete the country because there was no country code provided.");
return;
}
var predictions = await _Service.GetGoogleMapsSuggestionFromQuery(user.countrycode, SearchTextFrom);
suggestionFrom = predictions;
}));
}
}
private List<Prediction> _suggestionFrom;
public List<Prediction> suggestionFrom
{
get { return _suggestionFrom; }
set
{
Set<List<Prediction>>(() => suggestionFrom, ref _suggestionFrom, value);
Debug.WriteLine(suggestionFrom.Count + " were received. Displayong them in the autosuggestbox");
foreach (Prediction prediction in _suggestionFrom)
{
Debug.WriteLine(("Predicition: " + prediction.description));
}
}
}
The objects are set and are not null.
So why don't they show up?
UPDATE
My Model
public class Prediction : ObservableObject
{
private string _description;
public string description
{
get { return _description; }
set{Set<string>(() => description, ref _description, value);}
}
private string _id;
public string id
{
get { return _id; }
set { Set<string>(() => id, ref _id, value); }
}
private List<MatchedSubstring> _matchedSubstrings;
public List<MatchedSubstring> matched_substrings
{
get { return _matchedSubstrings; }
set{Set<List<MatchedSubstring>>(() => matched_substrings, ref _matchedSubstrings, value);}
}
private string _place_id;
public string place_id
{
get { return _place_id; }
set { Set<string>(() => place_id, ref _place_id, value); }
}
private string _reference;
public string reference
{
get { return _reference; }
set { Set<string>(() => reference, ref _reference, value); }
}
private List<Term> _terms;
public List<Term> terms
{
get { return _terms; }
set { Set<List<Term>>(() => terms, ref _terms, value); }
}
private List<string> _types;
public List<string> types
{
get { return _types; }
set { Set<List<String>>(() => types, ref _types, value); }
}
public override string ToString()
{
return this.description;
}
}
Use
public ObservableCollection<Prediction> SuggestionFrom { get; set; }
instead
public List<Prediction> SuggestionFrom { get; set; }
ObservableCollection is notify user interface about any changes of your Predictions (add or delete)

With linked ComboBoxes, secondary combobox always starts empty

I've been trying for two days now to figure this out, and repeatedly searching for answers. It seems like it should be so simple.
The goal:
I have two comboboxs. One is for an object Country, the other is for CountrySubdivision. (EG USA has States, Canada has Provinces, etc.) I want to load the form and have both the Country and CountrySubdivision in place.
Project has a Country and CountrySubdivions. Country has an ObservableCollection of CountrySubdivision. All objects are INotifyCollectionChanged. So here's where it gets irritating. The bindings "work." In that, if I change the values and save, the proper values are put back into the database. However, every time I open up the application, CountrySubdivision starts empty. The backing value is still set, but the combo box isn't set.
Here are the applicable bindings:
<ComboBox
x:Name="uiProjectCountrySubdivisions"
DisplayMemberPath="Name"
SelectedItem="{Binding Project.ProjectCountrySubdivision}"
ItemsSource="{Binding Project.ProjectCountry.CountrySubdivisions}"
FontSize="10.667" />
<ComboBox
x:Name="uiProjectCountry"
SelectedItem="{Binding Project.ProjectCountry}"
ItemsSource="{Binding Countries}"
DisplayMemberPath="Name"
FontSize="10.667" />
And as requested, here are the (severly shorted) classes.
public class Project : BaseNotifyPropertyChanged
{
private Guid _projectId;
public virtual Guid ProjectId
{
get { return _projectId; }
set
{
if (_projectId != value)
{
_projectId = value;
OnPropertyChanged(() => ProjectId);
}
}
}
private string _projectName;
public virtual string ProjectName
{
get { return _projectName; }
set
{
if (_projectName != value)
{
_projectName = value;
OnPropertyChanged(() => ProjectName);
}
}
}
private string _projectNumber;
public virtual string ProjectNumber
{
get { return _projectNumber; }
set
{
if (_projectNumber != value)
{
_projectNumber = value;
OnPropertyChanged(() => ProjectNumber);
}
}
}
private string _projectAddressLineOne;
public virtual string ProjectAddressLineOne
{
get { return _projectAddressLineOne; }
set
{
if (_projectAddressLineOne != value)
{
_projectAddressLineOne = value;
OnPropertyChanged(() => ProjectAddressLineOne);
}
}
}
private string _projectAddressLineTwo;
public virtual string ProjectAddressLineTwo
{
get { return _projectAddressLineTwo; }
set
{
if (_projectAddressLineTwo != value)
{
_projectAddressLineTwo = value;
OnPropertyChanged(() => ProjectAddressLineTwo);
}
}
}
private string _projectCity;
public virtual string ProjectCity
{
get { return _projectCity; }
set
{
if (_projectCity != value)
{
_projectCity = value;
OnPropertyChanged(() => ProjectCity);
}
}
}
private string _projectZip;
public virtual string ProjectZip
{
get { return _projectZip; }
set
{
if (_projectZip != value)
{
_projectZip = value;
OnPropertyChanged(() => ProjectZip);
}
}
}
private Country _projectCountry;
public virtual Country ProjectCountry
{
get { return _projectCountry; }
set
{
if (_projectCountry != value)
{
_projectCountry = value;
OnPropertyChanged(() => ProjectCountry);
}
}
}
private CountrySubdivision _projectCountrySubdivision;
public virtual CountrySubdivision ProjectCountrySubdivision
{
get { return _projectCountrySubdivision; }
set
{
if (_projectCountrySubdivision != value)
{
_projectCountrySubdivision = value;
OnPropertyChanged(() => ProjectCountrySubdivision);
}
}
}
}
public class Country : BaseNotifyPropertyChanged
{
private int _id;
public virtual int CountryId
{
get { return _id; }
set
{
if (_id != value)
{
_id = value;
OnPropertyChanged(() => CountryId);
}
}
}
private string _name;
public virtual string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(() => Name);
}
}
}
private string _code;
public virtual string Code
{
get
{
return _code;
}
set
{
if (_code != value)
{
_code = value;
OnPropertyChanged(() => Code);
}
}
}
private ObservableCollection<CountrySubdivision> _countrySubdivisions = new ObservableCollection<CountrySubdivision>();
public virtual ObservableCollection<CountrySubdivision> CountrySubdivisions
{
get
{
return _countrySubdivisions;
}
set
{
if (_countrySubdivisions != value)
{
_countrySubdivisions = value;
OnPropertyChanged(() => CountrySubdivisions);
}
}
}
private string _subdivisionTypeShortDescription;
public virtual string SubdivisionTypeShortDescription
{
get
{
return _subdivisionTypeShortDescription;
}
set
{
if (_subdivisionTypeShortDescription != value)
{
_subdivisionTypeShortDescription = value;
OnPropertyChanged(() => SubdivisionTypeShortDescription);
}
}
}
private string _subdivisionTypeLongDescription;
public virtual string SubdivisionTypeLongDescription
{
get
{
return _subdivisionTypeLongDescription;
}
set
{
if (_subdivisionTypeLongDescription != value)
{
_subdivisionTypeLongDescription = value;
OnPropertyChanged(() => SubdivisionTypeLongDescription);
}
}
}
}
public class CountrySubdivision : BaseNotifyPropertyChanged
{
private int _id;
public virtual int CountrySubdivisionId
{
get { return _id; }
set
{
if (_id != value)
{
_id = value;
OnPropertyChanged(() => CountrySubdivisionId);
}
}
}
private string _name;
public virtual string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(() => Name);
}
}
}
private Country _country;
public virtual Country Country
{
get { return _country; }
set
{
if (_country != value)
{
_country = value;
OnPropertyChanged(() => Country);
}
}
}
}
public abstract class BaseNotifyPropertyChanged : INotifyPropertyChanged
{
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(Expression<Func<object>> propertySelector)
{
//Stuff that turns () => Property into a fired Property Changed event.
}
}
Please help!!
Is the SelectedItem on your ComboBox an object? If so, is it the exact same instance that exists in the "Countries" collection? Remember that for WPF to consider something "Selected" it must be able to find the "SelectedItem" in the "Countries" collection. If these types differ or instances differ that won't work.
I prefer binding my ComboBoxes as follows:
IDictionary<int, string> Countries
int? CountryId
<ComboBox
ItemsSource="{Binding Countries}"
SelectedValue="{Binding CountryId}"
SelectedValuePath="Key"
DisplayMemberPath="Value"/>
Still, as Alex asks, please list the relevant class definitions and relevant properties too.

Categories

Resources