I have a code for checkbox. Please tell me how to write it in MVVM?
There is a function that I can choose only one checkbox. In general I understand that I must to write command.
XAML:
<StackLayout>
<!-- Place new controls here -->
<ListView ItemsSource="{Binding Items}" ItemSelected="ListView_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<CheckBox HorizontalOptions="Start" Color="Black" CheckedChanged="CheckBox_CheckedChanged"
IsChecked="{Binding IsSelected}"
/>
<Label Text="meow" TextColor="Gray"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
CODE BEHIND
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainPageViewModel();
}
Model previousModel;
private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
if (previousModel != null)
{
previousModel.IsSelected = false;
}
Model currentModel = ((CheckBox)sender).BindingContext as Model;
previousModel = currentModel;
if (currentModel.IsSelected)
{
var viewModel = BindingContext as MainPageViewModel;
int index = viewModel.Items.IndexOf(currentModel);
}
}
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (previousModel != null)
{
previousModel.IsSelected = false;
}
Model currentModel = e.SelectedItem as Model;
currentModel.IsSelected = true;
previousModel = currentModel;
}
}
ViewModel
public class MainPageViewModel
{
public List<Model> Items { set; get; }
public MainPageViewModel()
{
List<Model> list = new List<Model>();
for (int i=0; i<10; i++)
{
list.Add(new Model { IsSelected = false });
}
Items = list;
}
}
Model
public class Model : INotifyPropertyChanged
{
bool isSelected;
public bool IsSelected
{
set
{
isSelected = value;
onPropertyChanged();
}
get => isSelected;
}
public event PropertyChangedEventHandler PropertyChanged;
void onPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
For an event to command use Corcav.Behavior nuget
https://github.com/corradocavalli/Corcav.Behaviors
...
xmlns:corcav="clr-namespace:Corcav.Behaviors;assembly=Corcav.Behaviors"
...
<CheckBox>
<corcav:Interaction.Behaviors>
<corcav:BehaviorCollection>
<corcav:EventToCommand EventName="CheckedChanged" Command="{Binding Path=CheckBoxChangedCommand}" Commandparameter="{Binding .}"/>
</corcav:BehaviorCollection>
</corcav:Interaction.Behaviors>
</CheckBox>
Add this command in ViewModel and write your logic
public ICommand CheckBoxChangedCommand{ get; set; }
...
CheckBoxChangedCommand= new Command<object>(CheckBoxChanged);
...
private void CheckBoxChanged(object obj)
{
//set all list/collection element to false with linq
if(obj is Model model)
{
model.IsSelected = true;
}
}
For now, CheckBox do not support Command. This issue has reported on Github and have not fixed. We could follow this enhancement. https://github.com/xamarin/Xamarin.Forms/issues/6606
You could use the InputKit instead. Install Xamarin.Forms.InputKit on NuGet.
It provides CheckChangedCommand.
CheckChangedCommand: (Command) Bindable Command, executed when check changed.
<input:CheckBox HorizontalOptions="Start" Color="Black" CheckChangedCommand="{Binding CheckBoxChangedCommand}">
Related
I am attempting to prevent my application from deleting a view and then creating a new one each time it's navigated around. I have a dashboard that will run a test program, if I select the settings view, then back to the dashboard, it has deleted the running test and initialized a new view. I need to keep the same view instance alive so that the test can continue to run while the user navigates to the settings view and back again but I cant exactly figure out how to successfully do that. I have attempted making the instance static but that doesn't seem to make a difference.
MainViewModel
class MainVM : ViewModelBase
{
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set { _currentView = value; OnPropertyChanged(); }
}
public ICommand DashboardCommand { get; set; }
public ICommand SettingsCommand { get; set; }
public static DashboardVM DashboardInstance { get; } = new DashboardVM();
public static SettingsVM SettingsInstance { get; } = new SettingsVM();
private void Dashboard(object obj) => CurrentView = DashboardInstance;
private void Settings(object obj) => CurrentView = SettingsInstance;
public MainVM()
{
DashboardCommand = new RelayCommand(Dashboard);
SettingsCommand = new RelayCommand(Settings);
// Startup Page
CurrentView = DashboardInstance;
}
}
ViewModelBase
public partial class ViewModelBase : ObservableObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
MainView - Navigation
<!-- Navigation Panel -->
<Grid HorizontalAlignment="Left" Width="76">
<Border Background="#3D5A8A" CornerRadius="10,0,0,10" />
<StackPanel Height="1200" Width="76">
<!-- Dashboard Button -->
<nav:Button Style="{StaticResource NavButton_Style}"
Command="{Binding DashboardCommand}"
IsChecked="True">
<Grid>
<Image Source="Images/dash_black_50.png"
Style="{StaticResource NavImage_Style}" />
<TextBlock Text="Dashboard"
Style="{StaticResource NavText_Style}" />
</Grid>
</nav:Button>
<!-- Settings Button -->
<nav:Button Style="{StaticResource NavButton_Style}"
Command="{Binding SettingsCommand}">
<Grid>
<Image Source="Images/gear_black_50.png"
Style="{StaticResource NavImage_Style}" />
<TextBlock Text="Settings"
Style="{StaticResource NavText_Style}" />
</Grid>
</nav:Button>
</StackPanel>
</Grid>
DashboardVM
class DashboardVM : ViewModelBase
{
enum TestItemStatus
{
Reset,
Queued,
InProgress,
Pass,
Fail
}
private readonly PageModel _pageModel;
private string _StartButtonText,
_WaveRelayEthernetText;
private bool isTestRunning;
public DashboardVM()
{
_pageModel = new PageModel();
_StartButtonText = "Start Test";
_WaveRelayEthernetText = string.Empty;
StartButtonCommand = new RelayCommand(o => StartButtonClick("StartButton"));
}
#region Text Handlers
public string StartButtonText
{
get { return _StartButtonText; }
set { _StartButtonText = value; NotifyPropertyChanged("StartButtonText"); }
}
public string WaveRelayEthernetText
{
get { return _WaveRelayEthernetText; }
set { _WaveRelayEthernetText = value; NotifyPropertyChanged("WaveRelayEthernetText"); }
}
#endregion
private bool TestRunning
{
get { return isTestRunning; }
set { isTestRunning = value;
if (isTestRunning) { StartButtonText = "Stop Test"; }
else { StartButtonText = "Start Test";
ResetTestItems();
}
NotifyPropertyChanged("TestRunning");
}
}
public ICommand StartButtonCommand { get; set; }
private void StartButtonClick(object sender)
{
if(TestRunning)
{
TestRunning = false;
}
else
{
SetTestItemsToQueued();
MessageBox.Show("Please plug in Tube 1");
// Start program.
TestRunning = true;
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.InProgress);
}
}
private string TestItemStatusEnumToString(TestItemStatus temp)
{
if (temp == TestItemStatus.Reset) { return string.Empty; }
else if (temp == TestItemStatus.Queued) { return "Queued"; }
else if (temp == TestItemStatus.InProgress) { return "In Progress"; }
else if (temp == TestItemStatus.Pass) { return "Pass"; }
else if (temp == TestItemStatus.Fail) { return "Fail"; }
else { return string.Empty; }
}
private void SetTestItemsToQueued()
{
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.Queued);
}
private void ResetTestItems()
{
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.Reset);
}
}
Image for reference:
My Issue was in the App.xaml, I link a DataTemplate file like this:
<ResourceDictionary Source="Utilities/DataTemplate.xaml" />
Inside the data template, I had this code that linked the views to the view models.
<ResourceDictionary [...]">
<DataTemplate DataType="{x:Type vm:DashboardVM}">
<view:Dashboard />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsVM}">
<view:Settings />
</DataTemplate>
</ResourceDictionary>
I changed that code to link the two to this:
<ResourceDictionary [...]>
<view:Dashboard x:Key="DashboardViewKey"/>
<view:Settings x:Key="SettingsViewKey"/>
<DataTemplate DataType="{x:Type vm:DashboardVM}">
<ContentControl Content="{StaticResource DashboardViewKey}" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsVM}">
<ContentControl Content="{StaticResource SettingsViewKey}" />
</DataTemplate>
</ResourceDictionary>
I am now receiveing the expected behavior of being able to navigate without the Dashboard constructor being called, thus the view does not destory and recreate.
I hope someone else finds this useful.
I'm pretty new to programming with WPF and C# and I have a question regarding the possibility to automatically check all the CheckBoxes in a Listbox. I'm developing a plugin for Autodesk Revit and, after having listed all the names of the rooms in a list box, I want to check them all using the button "Check All"
I've read the thread at this page but still, I'm not able to make it work. May someone help me with my code?
Here is what I've done:
XAML:
<ListBox x:Name='roomlist'
SelectionMode='Multiple'>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked='{Binding IsChecked}'
Content="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.InputBindings>
<KeyBinding Command="ApplicationCommands.SelectAll"
Modifiers="Ctrl"
Key="A" />
</ListBox.InputBindings>
<ListBox.CommandBindings>
<CommandBinding Command="ApplicationCommands.SelectAll" />
</ListBox.CommandBindings>
</ListBox>
C#
public partial class RoomsDistance_Form : Window
{
UIDocument _uidoc;
Document _doc;
public RoomsDistance_Form(Document doc, UIDocument uidoc)
{
InitializeComponent();
FilteredElementCollector collector = new FilteredElementCollector(doc)
.WhereElementIsNotElementType()
.OfCategory(BuiltInCategory.OST_Rooms);
List<String> myRooms = new List<String>();
foreach (var c in collector)
{
myRooms.Add(c.Name);
}
myRooms.Sort();
roomlist.ItemsSource = myRooms;
}
private void checkAllBtn_Click(object sender, RoutedEventArgs e)
{
foreach (CheckBox item in roomlist.Items.OfType<CheckBox>())
{
item.IsChecked = true;
}
}
public class Authority : INotifyPropertyChanged
{
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Thank you very much for your help!
In the thread you are linking to, they are setting the "IsChecked" on the data object (Authority), not the CheckBox control itself.
foreach (var a in authorityList)
{
a.IsChecked = true;
}
You have a binding to IsChecked that will update the Checkbox control when NotifyPropertyChanged() is called.
After having lost my mind in the effort i solved my problem by avoiding the Listbox.. I simply added single CheckBoxes in the StackPanel.
XAML:
<ScrollViewer Margin='10,45,10,100'
BorderThickness='1'>
<StackPanel x:Name='stack'
Grid.Column='0'></StackPanel>
</ScrollViewer>
C#:
foreach (var x in myRooms)
{
CheckBox chk = new CheckBox();
chk.Content = x;
stack.Children.Add(chk);
}
Not what i was looking for but now it works and that's the point.
Thank you for your help!
I usually use CheckBoxList in the following way:
In xaml:
<ListBox ItemsSource="{Binding ListBoxItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> //+some dimensional properties
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In xaml.cs:
public partial class MyWindow : Window
{
public ViewModel ViewModel {get; set; }
public MyWindow(ViewModel viewModel)
{
//keep all the mess in ViewModel, this way your xaml.cs will not end up with 1k lines
ViewModel = viewModel;
DataContext = ViewModel;
InitializeComponent();
}
void BtnClick_SelectAll(object sender, RoutedEventArgs e)
{
ViewModel.CheckAll();
}
}
ViewModel preparation:
public class ViewModel
{
public List<ListBoxItem> ListBoxItems { get; set; }
//InitializeViewModel()...
//UpdateViewModel()...
//other things....
public void CheckAll()
{
foreach (var item in ListBoxItems)
{
item.IsSelected = true;
}
}
public class ListBoxItem : INotifyPropertyChanged
{
public string Name { get; set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm trying to group various sliders elements to work together. In example if I have two elements, both are set to 50 (values goes from 0 to 100), the sum of the values must be always 100, so if I set one of the sliders to value 70, the other one must decrease dynamically to 30 value.
<ListView x:Name="listView"
ItemSource="myList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Slider x:Name="slider"
Maximum="100"
Minimum="0"
Value="{Binding MyValue}"
HorizontalOptions="FillAndExpand"
MinimumTrackColor="Black"
MaximumTrackColor="Black"
ThumbColor="{StaticResource myColor}"
ValueChanged="OnValueChanged"
PropertyChanging="ChangingSliderValue"/>
<Label Text="{Binding Source={x:Reference slider}, Path=Value, StringFormat='{0:0}'}"
FontSize="Medium"
FontAttributes="Bold"
HorizontalOptions="End"
WidhtRequest="40"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The C# class of my objects is
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _value;
private string _name;
private bool _someBool;
public int MyValue { get {return _value;} set {_value = value;} }
public string Name { get {return _name;} set {_name = value;} }
public bool SomeBool { get {return _someBool;} set {_someBool = value; OnPropertyChanged();} }
void OnPropertyChanged ([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And part of the class of the Xaml page is:
namespace MyApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SliderPage: ContentPage
{
private ObservableCollection<MyClass> list = new ObservableCollection<MyClass>();
...
public SliderPage ()
{
InitializeComponent();
InitList();
listView.ItemsSource = list;
...
}
private void InitList () ...
private void ChangingSliderValue (object sender, PropertyChangedEventArgs e)
{
//Code to stick together the slider...
}
}
Any suggestion of how to achieve my goal?
work but is not showing dynamically
You need to implement the interface INotifyPropertyChanged on property MyValue so that the element will update in runtime .
By the way , since you had used data binding in your project . It would be better to handle logic in ViewModel. So you could modify the code like following
in Xmal
<ListView
ItemsSource="{Binding MySource}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" HeightRequest="300" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Slider x:Name="slider"
Maximum="100"
Minimum="0"
Value="{Binding MyValue,Mode=TwoWay}"
HorizontalOptions="FillAndExpand"
MinimumTrackColor="Black"
MaximumTrackColor="Black"/>
<Label Text="{Binding Source={x:Reference slider}, Path=Value, StringFormat='{0:0}'}"
FontSize="20"
FontAttributes="Bold"
WidthRequest="40"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
in Code behind
Model
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Id { get; set; } // add this property to index the slider
private int _value;
private string _name;
private bool _someBool;
public int MyValue {
get
{
return _value;
}
set
{
if(_value!=value)
{
_value = value;
OnPropertyChanged("MyValue");
}
}
}
public string Name { get { return _name; } set { _name = value; } }
public bool SomeBool { get { return _someBool; } set { _someBool = value; OnPropertyChanged("SomeBool"); } }
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ViewModel
public class MyViewModel
{
public ObservableCollection<MyClass> MySource { get; set; }
bool isFirstLoad = true;
public MyViewModel()
{
MySource = new ObservableCollection<MyClass>() {
new MyClass(){MyValue = 50,Id=0 },
new MyClass(){MyValue = 50,Id=1 },
};
foreach (MyClass model in MySource)
{
model.PropertyChanged += Model_PropertyChanged;
}
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName== "MyValue")
{
//handle your logic here as you need
MyClass model = sender as MyClass;
foreach (MyClass item in MySource)
{
if (model.Id != item.Id)
{
item.MyValue = 100 - model.MyValue;
}
}
}
}
}
ContentPage
public xxxPage()
{
InitializeComponent();
BindingContext = new MyViewModel();
}
I'm developing an app with xamarin forms and the MVVM pattern. I have a page with a listview that has three buttons but all the time with only 2 visibles and change the visibility of two of them when I press a button. The problem is that for the first ten items it works like supposed to be, press the button and dissapear and appear the other, but after the 10th item when I press the button it dissapear but the other doesn't appear until I scrool the list view to a position where the item is out of the screen. When the item is out of the screen and come back to be on the screen, the button appear. The visibility of the buttons is controlled changing a boolean property that is binded to the IsVisible property of the button and one of them with a converter to negate the value of the property. This is a repository that you can clone and see the code and test, maybe is something with my Visual Studio.
Initially, I thought it could be for a race condition and made the method that change the variable synchronous but it doesn't work.
This is my list view
<ListView ItemsSource="{Binding Items}"
HasUnevenRows="True"
SeparatorVisibility="None"
IsRefreshing="False">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"/>
<StackLayout Orientation="Horizontal">
<Button Text="One"
HorizontalOptions="CenterAndExpand"
TextColor="Green"
BackgroundColor="White"
BorderColor="Green"
BorderWidth="1"
WidthRequest="150" />
<Button Text="Two"
HorizontalOptions="CenterAndExpand"
BackgroundColor="Green"
TextColor="White"
Command="{Binding TestCommand}"
WidthRequest="150"
IsVisible="{Binding TestVariable, Converter={StaticResource negate}}" />
<Button Text="Three"
HorizontalOptions="CenterAndExpand"
BackgroundColor="Red"
Command="{Binding TestCommand}"
TextColor="White"
WidthRequest="150"
IsVisible="{Binding TestVariable}" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The viewmodel
public class ListViewTestModel : BaseViewModel
{
private List<ListItemTestModel> items;
public List<ListItemTestModel> Items
{
get => items;
set
{
SetValue(ref items, value);
}
}
public ListViewTestModel()
{
List<ListItemTestModel> itemList = new List<ListItemTestModel>();
for (int i = 0; i < 40; i++)
{
itemList.Add(new ListItemTestModel { Name = "Test" });
}
Items = itemList;
}
}
And another view model that is binded to each item in the listView
public class ListItemTestModel : BaseViewModel
{
private bool testVariable;
public string Name { get; set; }
public bool TestVariable
{
get
{
return testVariable;
}
set
{
SetValue(ref testVariable, value);
}
}
public Command TestCommand { get; set; }
public ListItemTestModel()
{
TestCommand = new Command(() =>
{
TestMethod();
});
}
public void TestMethod()
{
TestVariable = !TestVariable;
}
}
the BaseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value))
{
return;
}
backingField = value;
OnPropertyChanged(propertyName);
}
}
And the codebehind of the page
public partial class MainPage : ContentPage
{
public ListViewTestModel ViewModel { get; }
public MainPage()
{
ViewModel = new ListViewTestModel();
BindingContext = ViewModel;
InitializeComponent();
}
}
I suggest listview Caching Strategy may case this issue, the default value is RetainElement for ListView, so using CachingStrategy="RecycleElement" in ListView.
About listview Caching Strategy, you can take a look:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/performance#caching-strategy
You should definitely go to ObservableCollection type for your items thus you'll be able to observe and display any changes
private ObservableCollection<ListItemTestModel> items;
public ObservableCollection<ListItemTestModel> Items
{
get => items;
set => SetValue(ref items, value);
}
And you should set your BindingContext AFTER the InitializeComponent() method or property changed will be propagate before your view is initialized.
public MainPage()
{
InitializeComponent();
BindingContext = new ListViewTestModel();;
}
public ListViewTestModel()
{
List<ListItemTestModel> itemList = new List<ListItemTestModel>();
for (int i = 0; i < 40; i++)
{
itemList.Add(new ListItemTestModel { Name = "Test" });
}
Items = new ObservableCollection<ListItemTestModel>(itemList);
}
I have a UI which displays a ListView in a ListView:
<ListView
SelectedIndex="{x:Bind ParentViewModel.SelectedParentIndex, Mode=TwoWay}"
ItemsSource="{x:Bind ParentViewModel.ParentViewModels, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:ParentViewModel">
<StackPanel>
<TextBlock Text="{Binding ParentName}" />
<ListView
SelectedIndex="{x:Bind SelectedChildIndex, Mode=TwoWay}"
ItemsSource="{Binding ChildViewModels, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:ChildViewModel">
<TextBlock Text="{Binding ChildName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When I click on a parent element the SelectedParentIndex gets set and when I click on a child element the SelectedChildIndex gets set.
My problem is that when I click on a child element i don't know to wich parent element it belongs because the SelectedParentIndex is not set. How can I solve this?
And the flow how it should be:
Just add an event in. Here is a compiled working example.
<ListView
ItemsSource="{Binding ParentViewModels, Mode=OneWay}"
SelectedIndex="{Binding SelectedParentIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedParent,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate >
<StackPanel>
<TextBlock Text="{Binding ParentName}" />
<ListView
ItemsSource="{Binding ChildViewModels, Mode=OneWay}"
SelectedIndex="{Binding SelectedChildIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedChild,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ChildName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here are the cs files. Please pay close attention to the structure.
The MasterViewModel is your DataContext for your View. It handles the SelectedParent, the SelectedParentIndex and your parents collection.
public class MasterViewModel : ViewModelBase
{
private ParentViewModel _SelectedParent;
public ParentViewModel SelectedParent
{
get { return _SelectedParent; }
set
{
_SelectedParent = value;
OnPropertyChanged("SelectedParent");
}
}
private int _SelectedParentIndex;
public int SelectedParentIndex
{
get { return _SelectedParentIndex; }
set
{
_SelectedParentIndex = value;
OnPropertyChanged("SelectedParentIndex");
}
}
public ObservableCollection<ParentViewModel> ParentViewModels
{
get; private set;
}
public MasterViewModel()
{
ParentViewModels = new ObservableCollection<ParentViewModel>();
LoadData();
}
private void LoadData()
{
for(int x = 0; x < 10; x++)
{
ParentViewModel parent = new ParentViewModel();
parent.ChildChangedEvent += Parent_ChildChangedEvent;
for(int y = 0; y < 20; y++)
{
ChildViewModel child = new ChildViewModel()
{ ChildName = "Child " + y };
parent.ChildViewModels.Add(child);
}
ParentViewModels.Add(parent);
}
}
private void Parent_ChildChangedEvent(object sender, EventArgs e)
{
SelectedParent = (ParentViewModel)sender;
}
}
Your ParentViewModel contains your SelectedChildIndex, your SelectedChild and your ChildViewModels collection. It also has a name property
Notice that I added an EventHandler to your ParentViewModel. When the SelectedChild is updated, it fires the event off. Then, we handle this event in the MasterViewModel where we can force the SelectedParent to update.
public class ParentViewModel : ViewModelBase
{
public String ParentName { get; set; }
private int _SelectedChildIndex;
public int SelectedChildIndex
{
get { return _SelectedChildIndex; }
set
{
_SelectedChildIndex = value;
OnPropertyChanged("SelectedChildIndex");
}
}
private ChildViewModel _SelectedChild;
public ChildViewModel SelectedChild
{
get { return _SelectedChild; }
set
{
_SelectedChild = value;
OnPropertyChanged("SelectedChild");
if (ChildChangedEvent != null)
{
ChildChangedEvent(this, new EventArgs());
}
}
}
public ObservableCollection<ChildViewModel> ChildViewModels
{
get; private set;
}
public event EventHandler ChildChangedEvent;
public ParentViewModel()
{
ChildViewModels = new ObservableCollection<ChildViewModel>();
}
}
Your ChildViewModel just has a name property.
public class ChildViewModel : ViewModelBase
{
private string _childName;
public string ChildName
{
get { return _childName; }
set
{
_childName = value;
OnPropertyChanged("ChildName");
}
}
}
The ViewModelBase just updates the UI
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
}