Combox SelectedItem does not apply when restoring from serialized ViewModel - c#

I'm facing a strange problem when using C# WPF and MVVM Pattern while restoring a ViewModel (serialized using Json.Net).
The idea of the software is - when closing the window - to persist the current Viewmodel state in a json file.
At the next startup the app just serarches for the json.
If there a file, then deserialize it and restore the ViewModel (set public properties).
If there is no file, then the viewmodel is created and default values are set.
Now my problem is, that when restoring it with the json file, a combobox containing a list of a custom type, the combobox has values but no SelectedItem. When creating the viewmodel instance and initiailizing the public properties with default values (doing this via the code behind) then everything is fine.
Here is some code that represents the "error":
View
<Window x:Class="CrazyWpf.MainWindow"
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:local="clr-namespace:CrazyWpf"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
Closing="Window_Closing"
Loaded="Window_Loaded">
<StackPanel
x:Name="rootElement"
Orientation="Vertical"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10">
<StackPanel.DataContext>
<local:DemoViewModel />
</StackPanel.DataContext>
<StackPanel
Orientation="Horizontal">
<Label
x:Name="lblID"
Width="30"
Content="ID:"/>
<TextBox
x:Name="tbID"
Width="50"
Margin="30,0,0,0"
Text="{Binding ID, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<StackPanel
Orientation="Horizontal">
<Label
x:Name="lblName"
Width="45"
Content="Name:"/>
<TextBox
x:Name="tbName"
Width="200"
Margin="15,0,0,0"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<StackPanel
Orientation="Horizontal">
<Label
x:Name="lblStai"
Width="60"
Content="Status:"/>
<ComboBox
x:Name="cbStati"
Width="200"
ItemsSource="{Binding StatusTypeList}"
SelectedItem="{Binding StatusType, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"/>
</StackPanel>
</StackPanel>
</Window>
Code Behind
using System;
using System.Windows;
using System.IO;
using Newtonsoft.Json;
namespace CrazyWpf
{
public partial class MainWindow : Window
{
private DemoViewModel dvm;
public MainWindow()
{
InitializeComponent();
this.dvm = (DemoViewModel)this.rootElement.DataContext;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
string filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "settings.json");
if (File.Exists(filePath))
File.Delete(filePath);
File.WriteAllText(filePath, JsonConvert.SerializeObject(this.dvm, Formatting.Indented));
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
string filePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "settings.json");
if (!File.Exists(filePath))
{ this.SetDefaultSettings(); return; }
DemoViewModel d = JsonConvert.DeserializeObject<DemoViewModel>(File.ReadAllText(filePath));
this.dvm.ID = d.ID;
this.dvm.Name = d.Name;
this.dvm.StatusType = d.StatusType;
}
}
}
BaseViewModel:
using System.ComponentModel;
namespace CrazyWpf
{
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace CrazyWpf
{
class DemoViewModel : BaseViewModel
{
[JsonIgnore]
private int id;
[JsonProperty(Order = 1)]
public int ID
{
get { return this.id; }
set
{
if (this.id != value)
{
this.id = value;
this.OnPropertyChanged("ID");
}
}
}
[JsonIgnore]
private string name;
[JsonProperty(Order = 2)]
public string Name
{
get { return this.name; }
set
{
if (this.name != value && value != null)
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
}
[JsonIgnore]
private StatusTyp statusType;
[JsonProperty(Order = 3)]
public StatusTyp StatusType
{
get { return this.statusType; }
set
{
if (this.statusType != value && value != null)
{
this.statusType = value;
this.OnPropertyChanged("StatusType");
}
}
}
[JsonIgnore]
private List<StatusTyp> statusTypeList;
[JsonProperty(Order = 4)]
public List<StatusTyp> StatusTypeList
{
get { return this.statusTypeList; }
set
{
if (this.statusTypeList != value && value != null)
{
this.statusTypeList = value;
this.OnPropertyChanged("StatusTypeList");
}
}
}
public DemoViewModel()
{
this.StatusTypeList = new Func<List<StatusTyp>>(() =>
{
var list = Enum.GetValues(typeof(Status))
.Cast<Status>()
.ToDictionary(k => (int)k, v => v.ToString())
.Select(e => new StatusTyp()
{
Value = e.Key,
Name = e.Value,
Status =
Enum.GetValues(typeof(Status))
.Cast<Status>().
Where(x =>
{
return (int)x == e.Key;
}).FirstOrDefault()
})
.ToList();
return list;
})();
}
}
public class StatusTyp
{
public int Value { get; set; }
public string Name { get; set; }
public Status Status { get; set; }
}
public enum Status
{
NotDetermined = 0,
Determined = 1,
Undeterminded = 2,
Unknown = 3
}
}

If you have an ItemsSource and a SelectedItem, the instance in SelectedItem MUST BE in the collection bound to ItemsSource. If it is not, then your bindings will not work as expected.
The control uses reference equality to determine which item in ItemsSource is the one in SelectedItem and update the UI. This normally isn't a problem as the control populates SelectedItem for you, but if you are updating from the ViewModel side, you have to make sure your references are managed correctly.
This can be an issue when serializing/deserializing your view model. Most common serializers don't track references, and so cannot restore these on deserialization. The same object may be referenced multiple places in the original object graph, but after deserialization you now have multiple instances of the original spread throughout the rehydrated graph. This won't work with your requirements.
What you have to do is, after deserializing, find the matching instance in your collection and substitute it for the instance in SelectedItem. Or, use a serializer that tracks instances.. The XAML serializer already does this, and is a surprisingly good xml serializer for .net object graphs.

Related

My ListView is showing the same item twice. How do I fix it?

I have a ComboBox that allows the user to select a category and a ListView that is bound to an ObservableCollection of items in the selected category. When the user selects a different category, the items in the collection are updated. Sometimes this works as expected, but sometimes the list of items is mangled. It shows a duplicate item when there should be two separate items.
The results seem to depend on which category I'm switching from. For example, if I switch from a category with no items to a category with two items, the same item is shown twice. But if I switch from a category with four items to that same category with two items, they are shown correctly.
Here is a repro:
MainPage.xaml
<Page
x:Class="ListViewDuplicateItem_Binding.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ListViewDuplicateItem_Binding">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
Grid.Column="0"
ItemsSource="{Binding ViewModel.Groups}"
SelectedItem="{Binding ViewModel.SelectedGroup, Mode=TwoWay}" />
<ListView
Grid.Row="1"
Grid.Column="0"
ItemsSource="{Binding ViewModel.Widgets}"
SelectedItem="{Binding ViewModel.SelectedWidget, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Widget">
<TextBlock Text="{Binding Id}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<local:MyControl
Grid.Row="1"
Grid.Column="1"
Text="{Binding ViewModel.SelectedWidget.Id, Mode=OneWay}" />
</Grid>
</Page>
MainPage.xaml.cs
using Windows.UI.Xaml.Controls;
namespace ListViewDuplicateItem_Binding
{
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
DataContext = this;
}
public MainViewModel ViewModel { get; } = new MainViewModel();
}
}
MainViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
namespace ListViewDuplicateItem_Binding
{
public class MainViewModel : INotifyPropertyChanged
{
private string _selectedGroup;
private Widget _selectedWidget;
public MainViewModel()
{
PropertyChanged += HomeViewModel_PropertyChanged;
SelectedGroup = Groups.First();
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> Groups { get; } = new ObservableCollection<string>(DataSource.AllGroups);
public string SelectedGroup
{
get => _selectedGroup;
set
{
if (_selectedGroup != value)
{
_selectedGroup = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedGroup)));
}
}
}
public Widget SelectedWidget
{
get => _selectedWidget;
set
{
if (_selectedWidget != value)
{
_selectedWidget = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedWidget)));
}
}
}
public ObservableCollection<Widget> Widgets { get; } = new ObservableCollection<Widget>();
private void HomeViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SelectedGroup))
{
var widgetsToLoad = DataSource.GetWidgetsForGroup(SelectedGroup);
// Add widgets in this group
widgetsToLoad.Except(Widgets).ToList().ForEach(w => Widgets.Add(w));
// Remove widgets not in this group
Widgets.Except(widgetsToLoad).ToList().ForEach(w => Widgets.Remove(w));
// Select the first widget
if (SelectedWidget == null && Widgets.Any())
{
SelectedWidget = Widgets.First();
}
}
}
}
}
DataSource.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace ListViewDuplicateItem_Binding
{
public static class DataSource
{
public static ObservableCollection<string> AllGroups { get; } = new ObservableCollection<string>
{
"First Widget",
"First Two Widgets",
"Last Two Widgets",
"All Widgets",
"None"
};
public static List<Widget> AllWidgets { get; } = new List<Widget>
{
new Widget()
{
Id = 1,
},
new Widget()
{
Id = 2,
},
new Widget()
{
Id = 3,
},
new Widget()
{
Id = 4,
}
};
public static List<Widget> GetWidgetsForGroup(string group)
{
switch (group)
{
case "First Widget":
return new List<Widget> { AllWidgets[0] };
case "First Two Widgets":
return new List<Widget> { AllWidgets[0], AllWidgets[1] };
case "Last Two Widgets":
return new List<Widget> { AllWidgets[2], AllWidgets[3] };
case "All Widgets":
return new List<Widget>(AllWidgets);
default:
return new List<Widget>();
}
}
}
}
Widget.cs
namespace ListViewDuplicateItem_Binding
{
public class Widget
{
public int Id { get; set; }
}
}
MyControl.xaml
<UserControl
x:Class="ListViewDuplicateItem_Binding.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBox Text="{x:Bind Text, Mode=TwoWay}" />
</UserControl>
MyControl.xaml.cs
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace ListViewDuplicateItem_Binding
{
public sealed partial class MyControl : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(MyControl), new PropertyMetadata(null));
public MyControl()
{
InitializeComponent();
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
}
This only seems to occur when the project includes a custom control that uses the Binding markup.
In the example above, if MyControl is removed from MainPage.xaml, it works as expected.
Likewise, if <local:MyControl Text="{Binding ViewModel.SelectedWidget.Id}" /> is changed to <local:MyControl Text="{x:Bind ViewModel.SelectedWidget.Id}" />, the example works as expected
This appears to be a bug in the ListView control, but you can work around it by using {x:Bind} compiled bindings.
Edit: Upon further investigation, the custom control may have been a red herring. Changing the custom control to a standard TextBox does not resolve the issue as I previously thought. The issue can be reproduced without the custom control. Nonetheless, using {x:Bind} or removing the control entirely does resolve the issue in this case.
Updating my project to use {x:Bind} (compiled bindings) appeared to resolve the issue, but a week later I unexpectedly started seeing duplicate items in my ListView again. This time I discovered three other factors that contributed to this issue.
I added a FallbackValue to the TextBoxes bound to the SelectedItem so they would be cleared when no item was selected. If I remove the FallbackValue, the list items are not duplicated. However, I need this setting.
I discovered that the order in which I add and remove items with the ObservableCollection bound to the ListView is important. If I add new items first and then remove old items, list items are duplicated. If I remove old items first and then add new items, the items are not duplicated. However, I'm using AutoMapper.Collection to update this collection, so I have no control over the order.
A colleague suggested that this bug may be related to the ListView.SelectedItem. I discovered that if I set the selected item to null before removing it from the collection, list items are not duplicated. This is the solution I am now using.
Here's an example:
// This resolves the issue:
if (!widgetsToLoad.Contains(SelectedWidget))
{
SelectedWidget = null;
}
// AutoMapper.Collection updates collections in this order. The issue does not occur
// if the order of these two lines of code is reversed.
{
// Add widgets in this group
widgetsToLoad.Except(Widgets).ToList().ForEach(w => Widgets.Add(w));
// Remove widgets not in this group
Widgets.Except(widgetsToLoad).ToList().ForEach(w => Widgets.Remove(w));
}
For a full repro, replace the code blocks in the question with these changes:
MainPage.xaml
<Page
x:Class="ListViewDuplicateItem_Fallback.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ListViewDuplicateItem_Fallback">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
Grid.Column="0"
ItemsSource="{x:Bind ViewModel.Groups}"
SelectedItem="{x:Bind ViewModel.SelectedGroup, Mode=TwoWay}" />
<ListView
Grid.Row="1"
Grid.Column="0"
ItemsSource="{x:Bind ViewModel.Widgets}"
SelectedItem="{x:Bind ViewModel.SelectedWidget, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Widget">
<TextBlock Text="{x:Bind Id}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBox
Grid.Row="1"
Grid.Column="1"
Text="{x:Bind ViewModel.SelectedWidget.Id, Mode=OneWay, FallbackValue=''}" />
</Grid>
</Page>
MainViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
namespace ListViewDuplicateItem_Fallback
{
public class MainViewModel : INotifyPropertyChanged
{
private string _selectedGroup;
private Widget _selectedWidget;
public MainViewModel()
{
PropertyChanged += HomeViewModel_PropertyChanged;
SelectedGroup = Groups.First();
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> Groups { get; } = new ObservableCollection<string>(DataSource.AllGroups);
public string SelectedGroup
{
get => _selectedGroup;
set
{
if (_selectedGroup != value)
{
_selectedGroup = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedGroup)));
}
}
}
public Widget SelectedWidget
{
get => _selectedWidget;
set
{
if (_selectedWidget != value)
{
_selectedWidget = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedWidget)));
}
}
}
public ObservableCollection<Widget> Widgets { get; } = new ObservableCollection<Widget>();
private void HomeViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SelectedGroup))
{
var widgetsToLoad = DataSource.GetWidgetsForGroup(SelectedGroup);
// This resolves the issue:
//if (!widgetsToLoad.Contains(SelectedWidget))
//{
// SelectedWidget = null;
//}
// AutoMapper.Collection updates collections in this order. The issue does not occur
// if the order of these two lines of code is reversed. I do not simply clear the
// collection and reload it because this clears the selected item even when it is in
// both groups, and the animation is much smoother if items are not removed and reloaded.
{
// Add widgets in this group
widgetsToLoad.Except(Widgets).ToList().ForEach(w => Widgets.Add(w));
// Remove widgets not in this group
Widgets.Except(widgetsToLoad).ToList().ForEach(w => Widgets.Remove(w));
}
// Select the first widget
if (SelectedWidget == null && Widgets.Any())
{
SelectedWidget = Widgets.First();
}
}
}
}
}
DataSource.cs
using System.Collections.Generic;
using System.Linq;
namespace ListViewDuplicateItem_Fallback
{
public static class DataSource
{
public static List<string> AllGroups { get; set; } = new List<string> { "Group 1", "Group 2", "Group 3" };
public static List<Widget> AllWidgets { get; set; } = new List<Widget>(Enumerable.Range(1, 11).Select(widgetId => new Widget { Id = widgetId }));
public static List<Widget> GetWidgetsForGroup(string group)
{
switch (group)
{
case "Group 1":
return AllWidgets.Take(4).ToList();
case "Group 2":
return AllWidgets.Skip(4).Take(4).ToList();
case "Group 3":
return AllWidgets.Take(1).Union(AllWidgets.Skip(8).Take(3)).ToList();
default:
return new List<Widget>();
}
}
}
}

Couldn't load combobox itemsource after deserialization in WPF

I'm facing a problem where I can't reload a serialiazed object (using NewtonsoftJson) into cascading comboboxes. I'm also using the Prism MVVM lib.
While at first start my application is working as expected:
so I am able to select values from the second combobox based on the first combobox, but when I save the model and reload it I've two main issues:
The SelectedItem property never get set (even the debugger shows that it's not null)
The second combobox stays empty eventhough the values seems to be loaded, that looks like:
What am I doing wrong here? Also, I don't like the ComboboxSelectionChanged approach, so maybe someone can point me to the MVVM based approach.
Here is the minimal working example:
MainWindow.xaml.cs
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
ConstructRandomData();
DataContext = viewModel;
}
private void ConstructRandomData()
{
// Construct data
for (int i = 0; i < 5; i++)
{
var ids = new List<SubId>();
for (int r = 0; r < 10; r++)
{
ids.Add(
new SubId
{
Name = $"Id_{i}.{r}"
}
);
}
viewModel.MainIds.Add(
new MainId
{
Name = $"MainId{i}",
SubIds = ids
});
}
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox combo = sender as ComboBox;
if (combo.SelectedItem is MainId selectedItem)
{
var subIdList = (from mainId in viewModel.MainIds
where mainId.Name.Equals(selectedItem.Name)
select mainId.SubIds).First();
viewModel.SubIds.Clear();
viewModel.SubIds.AddRange(subIdList.ToArray());
}
}
private void SaveButtton_Click(object sender, RoutedEventArgs e)
{
File.WriteAllText("savedData.json", JsonConvert.SerializeObject(viewModel));
}
private void LoadButton_Click(object sender, RoutedEventArgs e)
{
ViewModel deserializedModel = JsonConvert.DeserializeObject<ViewModel>(File.ReadAllText("savedData.json"));
viewModel.MainIds = deserializedModel.MainIds;
viewModel.SubIds = deserializedModel.SubIds;
}
}
public class ViewModel : BindableBase
{
public ObservableCollection<MainId> MainIds { get; set; } = new ObservableCollection<MainId>();
public ObservableCollection<SubId> SubIds { get; set; } = new ObservableCollection<SubId>();
private MainId mainId;
public MainId SelectedMainId
{
get { return mainId; }
set { SetProperty(ref mainId, value); }
}
private SubId selectedId;
public SubId SelectedId
{
get { return selectedId; }
set { SetProperty(ref selectedId, value); }
}
}
public class MainId : BindableBase
{
private string name;
public string Name
{
get { return name; }
set
{
SetProperty(ref name, value);
}
}
public List<SubId> SubIds { get; set; } = new List<SubId>();
}
public class SubId : BindableBase
{
private string name;
public string Name
{
get { return name; }
set { SetProperty(ref name, value); }
}
}
The MainWindow.xaml
<Window x:Class="CascadingComboBox.MainWindow"
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:local="clr-namespace:CascadingComboBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Margin="30">
<ComboBox Margin="5" Width="150"
ItemsSource="{Binding MainIds}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedMainId}"
SelectionChanged="ComboBox_SelectionChanged"/>
<ComboBox Margin="5" Width="150"
ItemsSource="{Binding SubIds}"
SelectedItem="{Binding SelectedId}"
DisplayMemberPath="Name"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="10">
<Button Margin="5" Width="50" Content="Save" Click="SaveButtton_Click" />
<Button Margin="5" Width="50" Content="Load" Click="LoadButton_Click"/>
</StackPanel>
</StackPanel>
</Window>
During serialization, SelectedItem contains an object from ComboBoxItems collection.
But after deserialization this is no longer true: now SelectedItem is a new instance, even though it has same content as one of the items in ComboBoxItems. This is how Json.NET works by default.
You can fix that by changing PreserveReferencesHandling option like this:
var jsonSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects };
JsonConvert.SerializeObject(model, Formatting.Indented, jsonSettings);
...
model = JsonConvert.DeserializeObject<List<Person>>(json, jsonSettings);
https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_PreserveReferencesHandling.htm

How do I properly bind a WPF TreeView control and effectively display a hierarchy of the data?

Ok, so I normally wait until the last possible moment to submit a question but I could really use some help.
I'm attempting to bind an ObservableCollection<Contact_Info> to a WPF TreeView Control. Contact_Info contains properties for contact information and implements INotifyPropertyChanged. It also contains a list of more detailed information for the given Contact.
I'm having trouble with 2 things. 1) I need to display on the TreeViewItem header the name of the Company however, a lot of the Contacts have a missing Company Name in which, I want to use the First and Last name. I have attempted to use things like FallBackValue and TargetNullValue but the CompanyName is not null, its just empty so I can't seem to get that right. Do I need a converter? 2) I cannot seem to get the hierarchy correct in that, I want all Contacts of the same AccountID to be grouped together within a TreeViewItem. Currently, I can only get the Company Name to display on the TreeViewItem and expanding it displays a blank value and there are still duplicate Company Names displayed..
Code for my attempt at a HierarchicalDataTemplate placed under the DockPanel.Resources (not sure if that's the right place either).
<local:Main_Interface x:Key="MyList" />
<HierarchicalDataTemplate DataType = "{x:Type
local:Contact_Info}" ItemsSource = "{Binding Path=OppDataList}">
<TextBlock Text="{Binding Path=CompanyName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType = "{x:Type sharpspring:SharpspringOpportunityDataModel}" >
<TextBlock Text="{Binding Path=ProjectName}"/>
</DataTemplate>
The above code is the most recent thing I have tried among countless other ways. I feel that I'm really close but at this point I could really use some help from SO..
The other classes are a little long so let me know if you need more. Any help provided is greatly appreciated!
Edit 1: Contact_Info
public class Contact_Info : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal SharpspringLeadDataModel LeadDataModel;
internal SharpspringOpportunityDataModel OppData;
public List<SharpspringOpportunityDataModel> OppDataList { get; set; }// Should i make this ObservableCollection?
public Project_Info ProjectInfo { get; set; }
public bool IsDirty { get; set; }
public bool IsNew { get; set; }
#region Properties
public long AccountID
{
get => LeadDataModel.AccountID;
set
{
if (value != LeadDataModel.AccountID)
{
LeadDataModel.AccountID = value;
NotifyPropertyChanged();
}
}
}
public long LeadID
{
get => LeadDataModel.LeadID;
set
{
if (value != LeadDataModel.LeadID)
{
LeadDataModel.LeadID = value;
NotifyPropertyChanged();
}
}
}
// Lead Info
public string CompanyName
{
get => LeadDataModel.CompanyName;
set
{
if (value != LeadDataModel.CompanyName)
{
LeadDataModel.FaxNumber = value;
NotifyPropertyChanged();
}
}
}
public Contact_Info(SharpspringLeadDataModel lead, SharpspringOpportunityDataModel opp)
{
LeadDataModel = lead;
OppData = opp;
ProjectInfo = new Project_Info(this);
}
public SharpspringLeadDataModel GetDataModel()
{
return LeadDataModel;
}
public SharpspringOpportunityDataModel GetOppModel()
{
return OppData;
}
public Project_Info GetProjectInfoModel()
{
return ProjectInfo;
}
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
IsDirty = true;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Edit 2: Currently populates tree but does not group Companies together...
private void PopulateTreeView(List<SharpspringLeadDataModel> lead_list, List<SharpspringOpportunityDataModel> opp_list)
{
Contacts = new ObservableCollection<Contact_Info>();
var complete2 =
from lead in lead_list
let oppList = from o in opp_list
where o.PrimaryLeadID == lead.LeadID
select o
select new Contact_Info()
{
LeadDataModel = lead,
OppData = oppList.DefaultIfEmpty(new SharpspringOpportunityDataModel()).First(),
OppDataList = oppList.ToList(),
};
Contacts = complete2.ToObservableCollection();
Lead_Treeview.ItemsSource = Contacts;
}
I think your problem is really with using List instead of ObservableCollection for OppDataList. Using your code (reduced to minimal needed) but with the ObservableCollection driving the collection it works fine for me.
Note that my code will work fine with List even because its all static but in a proper app you'll need OC to track changes in collection.
Disclaimer: I'm using code behind to answer this question to keep it simple but I do not recommend any such use in any real app
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
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:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="240" Width="320">
<TreeView ItemsSource="{Binding Contacts}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Contact_Info}" ItemsSource="{Binding Path=OppDataList}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type local:SharpspringOpportunityDataModel}">
<TextBlock Text="{Binding Path=ProjectName}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Path=CompanyName}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using WpfApp1.Annotations;
namespace WpfApp1
{
public partial class MainWindow : INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<Contact_Info> Contacts { get; } =
new ObservableCollection<Contact_Info>
{
new Contact_Info
{
CompanyName = "Apple",
OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}}
},
new Contact_Info
{
CompanyName = "Google",
OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}}
},
new Contact_Info
{
CompanyName = "Microsoft",
OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}}
}
};
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Contact_Info
{
public string CompanyName { get; set; }
public ObservableCollection<SharpspringOpportunityDataModel> OppDataList { get; } =
new ObservableCollection<SharpspringOpportunityDataModel>();
}
public class SharpspringOpportunityDataModel
{
public string ProjectName { get; set; }
}
}
Screenshot
based on my understanding your OppDataList is the sub property for the given Contract info and you want it to be collapsible.
In such case you can do
<ListBox ItemsSource="{Binding YourContactList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding ProjectName}">
<Expander Header="{Binding CompanyName}">
<ListBox ItemsSource="{Binding OppDataList}">
<!--Your OppDataList display-->
</ListBox>
</Expander>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
End result will look similar to this
+Contract1
-Contract2
-Project2
-Company2
-Opp
1
2
3
+Contract3

Wpf datacontext binding using MVVM between viewmodel and view

I just started learning MVVM and here is what seems to be basic question but I spent whole day trying to figure it out.
I have a solution that contains 3 projects one for Model, one for ViewModel and one for View. The Model contains a class that has 2 properties Text and CheckStatus.
The ViewModel has a list called listOfItems that has three items, each item has these 2 properties from the Model.
The View has a listView inside it there is a CheckBox. What is the proper way to bind the CheckBox content to the property Text?
Here is the model
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TheModel
{
public class CheckBoxListModel : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
RaiseChanged("Text");
}
}
private bool checkStatus;
public bool CheckStatus
{
get { return checkStatus; }
set
{
checkStatus = value;
RaiseChanged("CheckStatus");
}
}
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Here is the view model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using TheModel;
namespace TheViewModel
{
public class TheViewModel
{
public List<CheckBoxListModel> ListOfItems { get; set; }
public TheViewModelClass()
{
ListOfItems = new List<CheckBoxListModel>
{
new CheckBoxListModel
{
CheckStatus = false,
Text = "Item 1",
},
new CheckBoxListModel
{
CheckStatus = false,
Text = "Item 2",
},
new CheckBoxListModel
{
CheckStatus = false,
Text = "Item 3",
}
};
}
public static implicit operator List<object>(TheViewModelClass v)
{
throw new NotImplementedException();
}
}
}
and here is the View XAML
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:TheView.Managers" xmlns:TheViewModel="clr-
namespace:TheViewModel;assembly=TheViewModel"
x:Class="TheView.Styles.ListViewDatabaseStyle">
<UserControl.DataContext>
<TheViewModel:TheViewModelClass/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<Button Content="Continue" Style="{StaticResource ButtonStyle}"
Margin="1104,27,40,40"/>
<ListView x:Name="listView1" SelectionMode="Multiple"
Style="{StaticResource ListViewStyle}" Margin="10,55,10,10"
ctrl:ListViewLayoutManager.Enabled="true" ItemsSource="
{Binding TheViewModelClass}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Competency Items"
ctrl:ProportionalColumn.Width="1100"/>
</GridView>
</ListView.View>
<ListView.ItemContainerStyle >
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding
CheckedStatus}"/>
<Setter Property="HorizontalContentAlignment"
Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox
Click="CheckBox_Click"
Content="{Binding Path=TheViewModelClass.Text}"
IsChecked="{Binding
Path=TheViewModelClass.CheckedStatus}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</UserControl>
Here is the View behind code, I know I shouldn't have something here but where should that part go?
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System;
using System.Text;
using TheViewModel;
namespace TheView.Styles
{
public partial class ListViewDatabaseStyle : UserControl
{
public ListViewDatabaseStyle()
{
InitializeComponent();
}
public List<string> selectedNames = new List<string>();
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
var ChkBox = sender as CheckBox;
var item = ChkBox.Content;
bool isChecked = ChkBox.IsChecked.HasValue ? ChkBox.IsChecked.Value
: false;
if (isChecked)
selectedNames.Add(item.ToString());
else
selectedNames.Remove(item.ToString());
}
}
}
This is all quite ridiculous.
Here is a much easier way which involves no external libraries, no additional housekeeping classes and interfaces, almost no magic, and is very flexible because you can have viewmodels that contain other viewmodels, and you get to instantiate each one of them, so you can pass constructor parameters to them:
For the viewmodel of the main window:
using Wpf = System.Windows;
public partial class TestApp : Wpf.Application
{
protected override void OnStartup( Wpf.StartupEventArgs e )
{
base.OnStartup( e );
MainWindow = new MainView();
MainWindow.DataContext = new MainViewModel( e.Args );
MainWindow.Show();
}
}
For all other viewmodels:
This is in MainViewModel.cs:
using Collections = System.Collections.Generic;
public class MainViewModel
{
public SomeViewModel SomeViewModel { get; }
public OtherViewModel OtherViewModel { get; }
public Collections.IReadOnlyList<string> Arguments { get; }
public MainViewModel( Collections.IReadOnlyList<string> arguments )
{
Arguments = arguments;
SomeViewModel = new SomeViewModel( this );
OtherViewModel = new OtherViewModel( this );
}
}
This in MainView.xaml:
[...]
xmlns:local="clr-namespace:the-namespace-of-my-wpf-stuff"
[...]
<local:SomeView DataContext="{Binding SomeViewModel}" />
<local:OtherView DataContext="{Binding OtherViewModel}" />
[...]
As you can see, a viewmodel can simply be a member (child) of another viewmodel; in this case SomeViewModel and OtherViewModel are children of MainViewModel. Then, in the XAML file of MainView, you can just instantiate each of the child views and specify their DataContext by Binding to the corresponding child viewmodels.
First of all. Set dependencies of projects. ViewModel must have access Model. (View and Model projects do not have to reference to other projects.) If i were you i would make a StartUp Project to transfer the control to ViewModel.
This "StartUp" project should be WPF, all of others should be "class library" but don't forget to add the required references to projects (For example the system.xaml for your view project to create usercontrols.)
Projects dependencies:
- StartUp --> ViewModel;
(- ViewModel --> View; or avoid this with DI)
- ViewModel --> Model;
(I should make another project for interfaces just this is just my perversions.)
StartUp Project:
Now in your startup (WPF) project should contains in (app.xaml.cs):
protected override void OnStartup(StartupEventArgs e)
{
// delete the startupuri tag from your app.xaml
base.OnStartup(e);
//this MainViewModel from your ViewModel project
MainWindow = new MainWindow(new MainViewModel());
}
The only one thing (Window) in your startup wpf project (to display your UserControls).
MainWindow.xaml content:
<Window x:Class="StartUp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" WindowState="Maximized" WindowStyle="None" AllowsTransparency="True">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Control}"/>
</Window>
(and xaml.cs)
public partial class MainWindow : Window
{
public MainWindow(INotifyPropertyChanged ViewModel)
{
InitializeComponent();
this.DataContext = ViewModel;
this.Show();
}
}
And Thats all your StartUp WPF project.
In this way we gave the control to your ViewModel project.
(Okay, its just an extra, but i should make a "ViewService" to handle my UserControls)
Interface to find all of View and match the View with ViewModel.
public interface IControlView
{
INotifyPropertyChanged ViewModel { get; set; }
}
I created a singleton to store and match my views with my viewmodels. (You can skip this part.) I defined this in Model project.
public class ViewService<T> where T : IControlView
{
private readonly List<WeakReference> cache;
public delegate void ShowDelegate(T ResultView);
public event ShowDelegate Show;
public void ShowControl<Z>(INotifyPropertyChanged ViewModel)
{
if (Show != null)
Show(GetView<Z>(ViewModel));
}
#region Singleton
private static ViewService<T> instance;
public static ViewService<T> GetContainer
{
get
{
if (instance == null)
{
instance = new ViewService<T>();
}
return instance;
}
}
private ViewService()
{
cache = new List<WeakReference>();
var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()).Where(r => typeof(T).IsAssignableFrom(r) && !r.IsInterface && !r.IsAbstract && !r.IsEnum);
foreach (Type type in types)
{
cache.Add(new WeakReference((T)Activator.CreateInstance(type)));
}
}
#endregion
private T GetView<Z>(INotifyPropertyChanged ViewModel)
{
T target = default(T);
foreach (var wRef in cache)
{
if (wRef.IsAlive && wRef.Target.GetType().IsEquivalentTo(typeof(Z)))
{
target = (T)wRef.Target;
break;
}
}
if(target==null)
target = (T)Activator.CreateInstance(typeof(Z));
if(ViewModel != null)
target.ViewModel = ViewModel;
return target;
}
}
And now you have got a "service" to show your UserControls in the mainwindow from your
ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private IControlView _control;
public IControlView Control
{
get
{
return _control;
}
set
{
_control = value;
OnPropertyChanged();
}
}
public MainViewModel()
{ //Subscribe for the ViewService event:
ViewService<IControlView>.GetContainer.Show += ShowControl;
// in this way, here is how to set a user control to the window.
ViewService<IControlView>.GetContainer.ShowControl<ListViewDatabaseStyle>(new TheViewModel(yourDependencyItems));
//you can call this anywhere in your viewmodel project. For example inside a command too.
}
public void ShowControl(IControlView ControlView)
{
Control = ControlView;
}
//implement INotifyPropertyChanged...
protected void OnPropertyChanged([CallerMemberName] string name = "propertyName")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
If you don't want to use this "ViewService". Just create an UserControl instance, match DataContext of View with your ViewModel and give this view to Control property.
Here is your ViewModel with list (still in ViewMoldel project.)
public class TheViewModel
{
private readonly ObservableCollection<ISelectable> listOfItems;
public ObservableCollection<ISelectable> ListOfItems
{
get { return listOfItems; }
}
public ICommand SaveCheckedItemsText{
get{ return new RelayCommand(CollectNamesOfSelectedElements);}
}
public IEnumerable<ISelectable> GetSelectedElements
{
get { return listOfItems.Where(item=>item.CheckStatus); }
}
public TheViewModel(IList<ISelectable> dependencyItems)
{
listOfItems= new ObservableCollection<ISelectable>(dependencyItems);
}
//here is your list...
private List<string> selectedNames
//use this...
private void CollectNamesOfSelectedElements()
{
selectedNames = new List<string>();
foreach(ISelectable item in GetSelectedElements)
{
//you should override the ToString in your model if you want to do this...
selectedNames.Add(item.ToString());
}
}
}
RelayCommand article
View: (Keep here all of your usercontrols.)
In your UserControl (xaml):
<UserControl x:Class="View.ListViewDataStyle"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d">
<Button Command={Binding SaveCheckedItemsText}/>
<!-- Another content -->
<ListView ItemsSource="{Binding ListOfItems}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
And with interface here is the xaml.cs code (for UserControls):
public partial class ListViewDatabaseStyle : UserControl, IControlView
{
public ListViewDatabaseStyle ()
{
InitializeComponent();
}
public INotifyPropertyChanged ViewModel
{
get
{
return (INotifyPropertyChanged)DataContext;
}
set
{
DataContext = value;
}
}
}
And the last one is the Model project with your models:
public interface ISelectable
{
bool CheckStatus { get; set; }
}
public class CheckBoxListModel : INotifyPropertyChanged, ISelectable
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
RaiseChanged("Text");
}
}
private bool checkStatus;
public bool CheckStatus
{
get { return checkStatus; }
set
{
checkStatus = value;
RaiseChanged("CheckStatus");
}
}
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Excuse me for english grammar mistakes, i hope you understood my post.
Update:
Use the DI techn. to avoid the reference to view from viewmodel. DI service will inject the correct object with constructor injection.
<UserControl.DataContext>
<TheViewModel:TheViewModelClass/>
</UserControl.DataContext>
<ListView ItemsSource="{Binding ListOfItems}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

WPF databinding: view not updating from viewmodel

My WPF app is working in a strange way for me - some binding works, other not.
I have following situation:
A textbox - user provides an ID. Based on this ID an object is loaded or created. Some other properties are updated by values coming from the loaded/new object.
Binding for the ID textbox works fine. However, two other views (any other) not.
My code samples:
XAML:
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<TextBlock Text="ID" FontFamily="Segoe UI Light" />
<TextBox x:Name="TB_PacientID" Width="100px" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Path=PacientID}"/>
<TextBlock x:Name="TBL_NovyPacient" Text="novĂ˝ pacient" Margin="5,0,0,0" Foreground="Green" FontWeight="Bold" Visibility="{Binding Path=IsNewPacient,UpdateSourceTrigger=PropertyChanged,Converter={StaticResource BTVConverter}}"/>
</StackPanel>
<WrapPanel x:Name="WP_PacientData" Margin="-2,5,2,5" Visibility="{Binding PacientLoaded,Converter={StaticResource BTVConverter}}">
...
Viewmodel:
public int? PacientID
{
get
{
if (CurrentPacient == null)
return null;
return CurrentPacient.id;
}
set
{
if (value != null)
{
_pacient = App.instance.sessionData.serviceProxy.pacientById(value.Value);
if (_pacient == null)
{
CurrentPacient = new Pacient() { id = value.Value };
IsNewPacient = true;
}
else
{
CurrentPacient = _pacient;
}
OnPropertyChanged();
PacientLoaded = true;
}
}
}
// ...
public bool IsNewPacient
{
get{ return _isNewPacient; }
set
{
_isNewPacient = value;
OnPropertyChanged();
}
}
//...
public bool PacientLoaded
{
get{ return _pacientLoaded; }
set
{
_pacientLoaded = value;
OnPropertyChanged();
}
}
The idea:
User inputs the ID, an object is loaded or created and the WrapPanel is shown. If the object is newly created the TextBlock is shown as well.
The converters are working fine (tested in another window).
When the window loads, the binding is established well (if I set some fake values in ctor). When changing the ID in textbox, nothing other updates - except for the ID itself - the setter is fired well and the new value is read after OnPropertyChanged is called.
I hope I'm missing something very easy and stupid.
-Edit:
Current state:
TB_PacientID is working (updading), TBL_NovyPacient and WP_PacientData not working (updating).
I want:
All thee views updating from viewmodel (the code properties).
-Edit 2
I created a very simple example of my problem from scratch:
A window - two textboxes:
<Window x:Class="bindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox x:Name="TestTextBox" Text="{Binding ID, Mode=TwoWay}"/>
<TextBox x:Name="SecondTextBox" Text="{Binding IsNew, Mode=TwoWay}"/>
</StackPanel>
</Window>
Codebehind:
namespace bindingTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new TestViewModel();
}
}
}
And the viewmodel class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace bindingTest
{
public abstract class ViewModelBase
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class TestViewModel : ViewModelBase
{
private bool _attOne;
private int? id;
private bool _isNew;
public bool IsNew
{
get
{
return _isNew;
}
set
{
_isNew = value;
OnPropertyChanged();
}
}
public int? ID
{
get
{
return id;
}
set
{
this.id = value;
IsNew = true;
OnPropertyChanged();
}
}
}
}
And what I simply want - If I change the number in the first textbox I want to have True in the second textbox automatically.
Yes, I am stupid.
My ViewModel base class lost the INotifyPropertyChanged interface while merging from another project.
So I called the OnPropertyChanged, but it has been my own OnPropertyChanged instead of implementation of the interface which is WPF binding waiting for.
I had threethings to point in your code sample:
You should use a TwoWay binding for setting the ID.
Are you sure the _pacient = App.instance.sessionData.serviceProxy.pacientById(value.Value); code returns always the same object instance.
Are you correctly using the INotifyPropertyChanged interface in most cases you raising a property change events looks like this: RaisePropertyChanged('PropertyName'); you are invoking: 'OnPropertyChanged();'
Hope this helps...

Categories

Resources