Add children to user control with ViewModel? - c#

Question: How exactly do I add new children to my user control? Ive been told the viewmodel shouldnt know anything about the view and instead work with bindings. Now ive create an items control and set the path of my binding to a property MyLabels which contains and array of Labels. It doesnt work and honestly im not sure what the recommended way is to make it work.
The itemscontrol of my XAML (which is somewhere inside my user control):
<UserControl x:Class="Test.Main"
x:Name="this"
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"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="1000">
<ScrollViewer Height="400" Width="900">
<StackPanel Width="900">
<Grid x:Name="myGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding Path=MyLabels}"/>
</Grid>
<Image PreviewMouseDown="AddLabel""/>
</StackPanel>
</ScrollViewer>
</StackPanel>
</UserControl>
Here is my view model:
namespace Test {
public partial class Main {
public ObservableCollection<string> MyLabels { get; set; } = new ObservableCollection<string>();
public Main() {
InitializeComponent();
DataContext = this;
}
public void AddLabel(object sender, RoutedEventArgs e) {
for (int i = 0; i < 2; i++) {
MyLabels.Add("eee");
}
}
}
}

Set the DataContext of the view to an instance of your view model:
public Main() {
InitializeComponent();
DataContext = this;
}
The view model is however supposed to be a class of its own:
public MainWindow() {
InitializeComponent();
DataContext = new ViewModel();
}
And it shouldn't create any Label elements but strings:
public class ViewModel
{
public List<string> MyLabels { get; set; } new List<string>();
public ViewModel()
{
AddLabels();
}
public void AddLabels()
{
for (int i = 0; i < 5; i++)
{
MyLabels.Add("Sample");
}
}
}
Label is a user interface element type and these belong to the view only.
Also note that you can only bind to public properties.
It works now, only problem is im not sure how to add actual labels without violating the MVVM pattern.
You add the Label elements in the ItemTemplate of the ItemsControl. This template defines the appearance of an item in the source collection, i.e. a string in this case:
<ItemsControl ItemsSource="{Binding Path=MyLabels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

With following MVVM mimic(light weight flavor!), you should able to see the labels created in the View. Based on your update, still following generate strings.
ViewModel
public class ViewModel
{
public ObservableCollection<string> MyStrings { get; set; }
public ICommand AddStringCommand { get; private set; }
public ViewModel()
{
if (MyStrings == null) MyStrings = new ObservableCollection<string>();
for (int i = 0; i < 5; i++)
{
MyStrings.Add("string " + i);
}
AddStringCommand = new AddLabelCommand(AddString);
}
public void AddString()
{
MyStrings.Add("string " + (MyStrings.Count));
}
}
public class AddLabelCommand : ICommand
{
readonly Action _action = null;
public AddLabelCommand(Action commandToExecute)
{
_action = commandToExecute; //Handle null condition
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
}
MainWindow.xaml.cs (Code behind of your view)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
MainWindow.xaml (Inside View)
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="402" Width="540"
xmlns:local="clr-namespace:WpfApplication1">
<Window.Resources>
<local:ViewModel x:Key="ViewModel"/> <!--Initialize ViewModel inside XAML-->
</Window.Resources>
<Grid Height="367" Width="526" DataContext="{StaticResource ViewModel}">
<ItemsControl ItemsSource="{Binding Path=MyStrings}" Width="100" BorderBrush="Red">
<ItemsControl.ItemTemplate> <!--If you want to display your content in a control you can use ItemTemplate as suggested by #mm8-->
<DataTemplate>
<Label Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Add Label" Height="23" Width="100" Command="{Binding Path=AddStringCommand}"/>
</Grid>
</Window>
Hope this provide you a starting point!!

Related

Switch between views without losing data in WPF

I am working on a project that has one Main Window and two User Controls (Views).
The Main Window has a Menu on the left hand side with some buttons that activate one View or the other. On the right hand side of Main Window there's a Content Control that displays the CurrentView.
My idea was to have a Form for adding people on one View and a DataGrid displaying those people on the other. For that I made an ObservableColection that has People and bound that collection to the DataGrid on the corresponding View.
Everything is working as intended (with the Form I can add people to the collection and then those people get added to the DataGrid) except that when I add a person and then change the View to see if it got added to the DataGrid, nothing is added to the DataGrid but the headers.
If I put the same DataGrid with the same Binding in the first View (where the Form is) I can see that the people are getting added so I think the problem is that when I change Views I am losing the information about the collection.
``
This is the code I'm using to make the navigation between views available.
MainWindow
<Window x:Class="SwitchViewsDemo2.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:vm="clr-namespace:SwitchViewsDemo2.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:NavigationViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Center">
<Button Command="{Binding GoToAddPeopleCommand}" Content="Go To Add People" />
<Button Command="{Binding GoToDisplayPeopleCommand}" Content="Go To Display People" />
</StackPanel>
<ContentControl Grid.Column="1" Content="{Binding CurrentView}" />
</Grid>
</Window>
AddPeopleView
<UserControl x:Class="SwitchViewsDemo2.Views.AddPeopleView"
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"
xmlns:vm="clr-namespace:SwitchViewsDemo2.ViewModels"
mc:Ignorable="d" FontSize="28"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:AddPeopleViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Add People" />
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="First Name" />
<TextBox Text="{Binding FirstName}"/>
</StackPanel>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="1">
<TextBlock Text="Last Name" />
<TextBox Text="{Binding LastName}"/>
</StackPanel>
</Grid>
<StackPanel Grid.Row="1">
<Button Content="Add Person" Command="{Binding AddPersonCommand}"
Height="40" Width="200" VerticalAlignment="Top"/>
<DataGrid ItemsSource="{Binding People}"/>
</StackPanel>
</Grid>
</UserControl>
DisplayPeopleView
<UserControl x:Class="SwitchViewsDemo2.Views.DisplayPeopleView"
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"
xmlns:vm="clr-namespace:SwitchViewsDemo2.ViewModels"
mc:Ignorable="d" FontSize="28"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:DisplayPeopleViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel>
<TextBlock Text="Display People"/>
<DataGrid ItemsSource="{Binding People}"/>
</StackPanel>
</Grid>
</UserControl>
NavigationViewModel
using SwitchViewsDemo2.Utilities;
using System.Windows.Input;
namespace SwitchViewsDemo2.ViewModels
{
public class NavigationViewModel : Utilities.ViewModelBase
{
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set { _currentView = value; OnPropertyChanged(); }
}
public ICommand GoToAddPeopleCommand { get; set; }
public ICommand GoToDisplayPeopleCommand { get; set; }
private void AddPeople(object obj) => CurrentView = new AddPeopleViewModel();
private void DisplayPeople(object obj) => CurrentView = new DisplayPeopleViewModel();
public NavigationViewModel()
{
GoToAddPeopleCommand = new RelayCommand(AddPeople);
GoToDisplayPeopleCommand = new RelayCommand(DisplayPeople);
// Startup View
CurrentView = new AddPeopleViewModel();
}
}
}
ViewModelBase
using SwitchViewsDemo2.Models;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SwitchViewsDemo2.Utilities
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
private ObservableCollection<PersonModel> _people = new();
public ObservableCollection<PersonModel> People
{
get { return _people; }
set { _people = value; }
}
}
}
RelayCommand
using System;
using System.Windows.Input;
namespace SwitchViewsDemo2.Utilities
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object? parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object? parameter) => _execute(parameter);
}
}
DataTemplate(Resource Dictionary)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SwitchViewsDemo2.ViewModels"
xmlns:view="clr-namespace:SwitchViewsDemo2.Views">
<DataTemplate DataType="{x:Type vm:AddPeopleViewModel}">
<view:AddPeopleView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:DisplayPeopleViewModel}">
<view:DisplayPeopleView/>
</DataTemplate>
</ResourceDictionary>
App.xaml
<Application x:Class="SwitchViewsDemo2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SwitchViewsDemo2"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Utilities/DataTemplate.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Edit:
This is how I add the data to the People object
using SwitchViewsDemo2.Models;
using SwitchViewsDemo2.Utilities;
namespace SwitchViewsDemo2.ViewModels
{
public class AddPeopleViewModel : ViewModelBase
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; OnPropertyChanged(); }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; OnPropertyChanged(); }
}
public ButtonCommand AddPersonCommand { get; set; }
public void AddPerson()
{
PersonModel p = new PersonModel()
{
FirstName = FirstName,
LastName = LastName
};
FirstName = string.Empty;
LastName = string.Empty;
People.Add(p);
}
public AddPeopleViewModel()
{
AddPersonCommand = new ButtonCommand(AddPerson);
}
}
}
ButtonCommand:
using System;
using System.Windows.Input;
namespace SwitchViewsDemo2.Utilities
{
public class ButtonCommand : ICommand
{
public event EventHandler? CanExecuteChanged;
private Action _execute;
public ButtonCommand(Action execute)
{
_execute = execute;
}
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
_execute.Invoke();
}
}
}
Remove this:
<UserControl.DataContext>
<vm:AddPeopleViewModel/>
</UserControl.DataContext>
...and this:
<UserControl.DataContext>
<vm:DisplayPeopleViewModel/>
</UserControl.DataContext>
from the XAML markup of the views.
You are creating the AddPeopleViewModel and DisplayPeopleViewModel instances in the NavigationViewModel. You should not create additional instances of the view models in the views.
It's also unclear why're expecting adding an object to a collection in one view model should affect another one.
There should be only one collection of People. Now you have two in two different view models.

Trying to Create Custom Tooltip for LiveCharts WPF - Tooltip Doesn't Display Data

All, I am having a lot of problems with displaying a custom tooltip for my LiveCharts control Here is my code and what I have tried.
Chart C# Code:
public partial class Chart : UserControl
{
public ChartData chartData { get; set; }
public Chart()
{
chartData = new ChartData();
InitializeComponent();
Loaded += Control_Loaded;
}
private void Control_Loaded(object sender, RoutedEventArgs e)
{
// build here
DataContext = this;
chartData.Formatter = value => value.ToString("C", CultureInfo.CurrentCulture);
}
}
Chart XAML:
<UserControl x:Class="LandlordTenantDatabaseWPF.Chart"
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"
xmlns:local="clr-namespace:LandlordTenantDatabaseWPF"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:Chart}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="txtTitle" Grid.Row="0" Text="{Binding chartData.Title}" Foreground="#FF4B52E4" FontSize="36" TextAlignment="Center"/>
<lvc:CartesianChart x:Name="chart" Grid.Row="1" Series="{Binding chartData.SeriesCollection}" LegendLocation="Right" >
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Month" Labels="{Binding chartData.Labels}"></lvc:Axis>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis Title="Payments" LabelFormatter="{Binding chartData.Formatter}"></lvc:Axis>
</lvc:CartesianChart.AxisY>
<lvc:CartesianChart.DataTooltip>
<local:CustomersTooltip/>
</lvc:CartesianChart.DataTooltip>
</lvc:CartesianChart>
</Grid>
ToolTip C# Code:
public partial class CustomersTooltip : IChartTooltip
{
private TooltipData _data;
public CustomersTooltip()
{
InitializeComponent();
//LiveCharts will inject the tooltip data in the Data property
//your job is only to display this data as required
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public TooltipData Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged("Data");
}
}
public TooltipSelectionMode? SelectionMode { get; set; }
protected virtual void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ToolTip XAML:
<UserControl x:Class="LandlordTenantDatabaseWPF.CustomersTooltip"
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"
xmlns:wpf="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:local="clr-namespace:LandlordTenantDatabaseWPF"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
d:DataContext="{d:DesignInstance local:CustomersTooltip}"
Background="#E4555555" Padding="20 10" BorderThickness="2" BorderBrush="#555555">
<ItemsControl ItemsSource="{Binding Data.Points}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type wpf:DataPointViewModel}">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Title"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="LastName"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Phone"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="PurchasedItems"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Stroke="{Binding Series.Stroke}" Fill="{Binding Series.Fill}"
Height="15" Width="15"></Rectangle>
<TextBlock Grid.Column="1" Text="{Binding ChartPoint.Instance.(local:CustomerVm.Name)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
<TextBlock Grid.Column="2" Text="{Binding ChartPoint.Instance.(local:CustomerVm.LastName)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
<TextBlock Grid.Column="3" Text="{Binding ChartPoint.Instance.(local:CustomerVm.Phone),
StringFormat=Phone: {0}}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
<TextBlock Grid.Column="4" Text="{Binding ChartPoint.Instance.(local:CustomerVm.PurchasedItems),
StringFormat=Purchased Items: {0:N}}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ChartData Class:
public class ChartData : INotifyPropertyChanged
{
private SeriesCollection seriesCollection;
public SeriesCollection SeriesCollection
{
get { return seriesCollection; }
set
{
if (seriesCollection != value)
{
seriesCollection = value;
OnPropertyChanged("seriesCollection");
}
}
}
private string[] labels;
public string[] Labels
{
get { return labels; }
set
{
if(labels != value)
{
labels = value;
OnPropertyChanged("labels");
}
}
}
private string title;
public string Title
{
get { return title; }
set
{
if(title != value)
{
title = value;
OnPropertyChanged("title");
}
}
}
private Func<double, string> formatter;
public Func<double, string> Formatter
{
get { return formatter; }
set
{
if(formatter != value)
{
formatter = value;
OnPropertyChanged("formatter");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And the Window Containing the Chart:
private void AddNewSeries(List<double> lstPayments, string sSeriesName, SolidColorBrush colorBrush)
{
if (m_SeriesCollection == null)
{
m_SeriesCollection = new SeriesCollection
{ new ColumnSeries
{
Values = new ChartValues<double>(lstPayments),
Title = sSeriesName,
Fill = colorBrush
}
};
}
else
{
m_SeriesCollection.Add(
new ColumnSeries
{
Values = new ChartValues<double>(lstPayments),
Title = sSeriesName,
Fill = colorBrush
}
);
}
}
private void UpdateChartsBasedOnDates(Chart chart, string sChartTitle)
{ //Pass data to the chart
chart.chartData.SeriesCollection = m_SeriesCollection;
chart.chartData.Labels = GetFormattedDates(m_lstCombinedDates);
chart.chartData.Title = sChartTitle;
}
This all works perfectly when I just allow the default tooltip to be displayed on the chart. I am hung up on the fact that the chart data is supplied by a SeriesCollection, but the custom ToolTip, in the example on the LiveCharts website https://lvcharts.net/App/examples/v1/wpf/Tooltips%20and%20Legends doesn't use the SeriesCollection. Some of the charts displayed by my custom chart control have more than 1 series. I tried binding the Tooltip control to the SeriesCollection to get the data values, but that didn't work. Granted I may have done it incorrectly. Obviously the ToolTip XAML I am showing here is from the example code on the LiveCharts website, but I don't know where to go from here.
Is it possible to use make the tooltip use the series collection? What would be the best way for me to either use the Series Collection, or the ChartData class, or can I use
public TooltipData Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged("Data");
}
}
from the ToolTip C# code to display the data in the ToolTip?
I'm so confused.
Edit: I forgot to mention what my goal is. I want to display a tooltip that says
string.Format("{0} Payment: ${1}", SeriesName, PaymentAmount);
This would display Rent Payment: $1000. or MortgagePayment: $1000.00
Without being a deep LiveCharts Tooltip Wiz I did manage to get a version going that worked for me. My chart is also using a SeriesCollection, so maybe this can be helpful for you.
This is how I integrated it into my chart:
CartesianChart cartesianChart = ...;
_cartesianChart.DataTooltip = new CustomTooltip
{
SelectionMode = TooltipSelectionMode.OnlySender
};
This is my CustomToolTip class (I believe this is pretty much straight out of the documentation somewhere):
public partial class CustomTooltip : UserControl, IChartTooltip
{
private TooltipData _data;
public CustomTooltip()
{
InitializeComponent();
//LiveCharts will inject the tooltip data in the Data property
//your job is only to display this data as required
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public TooltipData Data
{
get => _data;
set
{
_data = value;
OnPropertyChanged("Data");
}
}
public TooltipSelectionMode? SelectionMode { get; set; }
protected virtual void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public partial class CustomTooltip : UserControl, IChartTooltip, INotifyPropertyChanged
{
private bool _contentLoaded;
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent()
{
if (_contentLoaded)
return;
System.Uri resourceLocater = new System.Uri(
"/Plotting/CustomTooltipDesign.xaml",
System.UriKind.Relative
);
System.Windows.Application.LoadComponent(this, resourceLocater);
_contentLoaded = true;
}
}
Then I built a XAML component, just like you. First super simple:
public partial class CustomTooltipDesign : UserControl
{
public CustomTooltipDesign()
{
DataContext = this;
}
}
Then I had a little more effort to build the actual tooltip:
<UserControl x:Class="expomrf4utility.CustomTooltipDesign"
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"
xmlns:expomrf4utility="clr-namespace:ExpoMRF4Utility"
xmlns:wpf="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance expomrf4utility:CustomTooltip}"
x:Name="Control" d:DesignWidth="1" d:DesignHeight="1">
<UserControl.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{Binding Foreground}"></Setter>
</Style>
<wpf:SharedConverter x:Key="SharedConverter"/>
<wpf:SharedVisibilityConverter x:Key="SharedVisibilityConverter"/>
<wpf:ChartPointLabelConverter x:Key="ChartPointLabelConverter"/>
<wpf:ParticipationVisibilityConverter x:Key="ParticipationVisibilityConverter"/>
<BooleanToVisibilityConverter x:Key="Bvc"></BooleanToVisibilityConverter>
<CollectionViewSource x:Key="GroupedPoints" Source="{Binding Data.Points}"/>
</UserControl.Resources>
<UserControl.Template>
<ControlTemplate>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" >
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Source={StaticResource GroupedPoints}}" Grid.IsSharedSizeScope="True">
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type wpf:DataPointViewModel}">
<Grid Margin="2" VerticalAlignment="Center" HorizontalAlignment="Center" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" SharedSizeGroup="Title"/>
<RowDefinition Height="Auto" SharedSizeGroup="Label"/>
<RowDefinition Height="Auto" SharedSizeGroup="Label"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="1" Text="{Binding Series.Title}" HorizontalAlignment="Left" FontWeight="Bold"/>
<TextBlock Grid.Row="2" Text="{Binding ChartPoint.Y, StringFormat='Value: {0} V/m'}" HorizontalAlignment="Left"/>
<TextBlock Grid.Row="3" Text="{Binding ChartPoint, Converter={StaticResource ChartPointLabelConverter}, StringFormat='Datetime: {0}'}" HorizontalAlignment="Left"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</ControlTemplate>
</UserControl.Template>
Note how I had to create bindings for the different properties that I wanted to display. To that end I slightly cheated when creating the data sets themselves. I used GLineSeries but I have a feeling it should work with other series as well:
int bandIndex = ...;
GearedValues<double> gearedValues = (some double[] data).AsGearedValues();
series.Add(new GLineSeries
{
Values = gearedValues,
LabelPoint = chartPoint => loggerData.Labels[(int)chartPoint.X].ToString(),
Tag = bandIndex,
Title = bandIndex.ToString()
});
Note that I chartPoint.X is the (zero-based) index of the the specific point (in X) - whereas chartPoint.Y is the Y-value of the point (also used in the XAML). Using this index I can look up in my list of names loggerData.Labels and return the timestamp associated with this index.

RaisePropertyChanged Triggering "set" On Different Property

I was working on a project when I came across an issue with RaisePropertyChanged from MVVM Light that I can't seem to figure out. When I try to raise a change for my list, the list does get updated, but as does the selected index value above. The value that is passed to my selected index appears to be influenced by what key was pressed to trigger the event (i.e. if I press "BACKSPACE", the value passed to the setter is "-1", whereas if I enter a letter, the value passed is "0")
I recreated a project which purely demonstrates the issue. Below is the main bit of logic found in the MainVeiwModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
_testItems = new List<TestItem>()
{
new TestItem() { Name = "Test1" },
new TestItem() { Name = "Test2" }
};
}
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
RaisePropertyChanged("SelectedIndex");
RaisePropertyChanged("SelectedText");
RaisePropertyChanged("TestList");
}
}
public string SelectedText
{
get
{
return _testItems[_selectedIndex].Name;
}
set
{
_testItems[_selectedIndex].Name = value;
RaisePropertyChanged("TextList");
}
}
public List<string> TextList
{
get
{
_textList = new List<string>();
if (_testItems != null && _testItems.Count > 0)
{
foreach (TestItem item in _testItems)
_textList.Add(item.Name);
}
return _textList;
}
set { _textList = value; }
}
private int _selectedIndex;
private List<string> _textList;
private List<TestItem> _testItems;
}
My XAML:
<Window x:Class="RaisePropertyBug.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:RaisePropertyBug"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding TextList, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding SelectedIndex, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Text="{Binding SelectedText, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
For context: I have a ComboBox which lists the names from a collection of items I have. There is an edit window where users can change names and other properties of these items. My goal is to have the ComboBox list update as the user edits the value. In my actual program, you are able to do this with the item at index 0, but any other index will automatically change to 0 as soon as a key is pressed and the RaisePropertyChanged() area is reached.
Check below code if it's working as per your requirement.
Use SelectedItem property of ComboBox and bind selecteditem to the edit screen/textbox. I've bind SelectedTestItem.Name propety here.
View -
<Window x:Class="StackOverflow.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:StackOverflow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding TestItems, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" SelectedValuePath="Name"
SelectedItem="{Binding SelectedTestItem, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Text="{Binding SelectedTestItem.Name, UpdateSourceTrigger=PropertyChanged}" Width="200"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
View.cs -
public partial class MainWindow : Window, INotifyPropertyChanged
{
private TestItem selectedTestItem;
public TestItem SelectedTestItem
{
get { return selectedTestItem; }
set
{
selectedTestItem = value;
RaisePropertyChanged("SelectedTestItem");
}
}
public List<TestItem> TestItems
{
get;
set;
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
var items = new List<TestItem>()
{
new TestItem() { Name = "Test1" },
new TestItem() { Name = "Test2" }
};
TestItems = items;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}
You don't even need INotifyPropertyChanged for this example. I'm not entirely certain of what you are trying to achieve, but this code will achieve what I have gleaned from your post.
<Window x:Class="RaisePropertyChangedExample.BindingExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Correct View" Width="150" Height="80">
<StackPanel Orientation="Vertical">
<ComboBox ItemsSource="{Binding Items}"
x:Name="ItemViews"
HorizontalAlignment="Stretch" VerticalAlignment="Center" DisplayMemberPath="Name"/>
<TextBox DataContext="{Binding SelectedItem, ElementName=ItemViews}" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
</StackPanel>
</Window>
and the supporting code
using System.Windows;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace RaisePropertyChangedExample
{
public partial class BindingExample : Window
{
public BindingExample()
{
InitializeComponent();
DataContext = new BindingExampleViewModel();
}
}
public class BindingExampleViewModel
{
public ObservableCollection<TestItemViewModel> Items { get; set; }
= new ObservableCollection<TestItemViewModel>(new List<TestItemViewModel>
{
new TestItemViewModel {Name = "Test1"},
new TestItemViewModel {Name = "Test2"}
});
}
public class TestItemViewModel
{
public string Name { get; set; }
}
}
Unless there is some need of the index of the selected Item, there is no real argument against simply exposing each item as a TestItemViewModel view model and binding the other controls directly to the selected item itself. If however other controls are bound to the members of the TestItemViewModel then it's still not necessarily true that you should implement INotifyPropertyChanged on that view model.
The following example will still display the correct information when wired up with the existing ViewModel:
<Window x:Class="RaisePropertyChangedExample.BindingExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Correct View" Width="150" Height="100">
<StackPanel Orientation="Vertical">
<ComboBox ItemsSource="{Binding Items}"
x:Name="Combo"
HorizontalAlignment="Stretch" VerticalAlignment="Center" DisplayMemberPath="Name"/>
<Grid DataContext="{Binding SelectedItem, ElementName=Combo}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
<Label Grid.Row="1" HorizontalAlignment="Stretch" Height="20" Content="{Binding Name}" />
</Grid>
</StackPanel>
</Window>
Normally
Updating after every keystroke can diminish performance and it denies the user the usual opportunity to backspace and fix typing errors before committing to the new value. see MS reference
however, this is only an issue if other processing occurs as the result of updating the source. If you are worried about the amount of processing involved you can switch to the default behaviour of LostFocus by simply omitting `UpdateSourceTrigger' declaration.

Items in ObservableCollection<string> do not update when bound to a WPF ListBox

I should like to manipulate an ObservableCollection of strings by binding it to the ItemsSource property of a ListBox and setting the item template to a TextBox.
My problem is that the items in the ObservableCollection do not get updated when I edit them in the TextBox items that the ListBox contains. What am I doing wrong?
The XAML of the minimum working example is
<Window x:Class="ListBoxUpdate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxUpdate" Height="300" Width="300"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Show items" Click="HandleButtonClick"/>
<TextBlock Grid.Row="1" x:Name="textBlock" />
</Grid>
<ListBox
Grid.Column="1"
ItemsSource="{Binding Strings, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding ., Mode=TwoWay}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
whilst the corresponding code-behind is
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace ListBoxUpdate
{
public partial class Window1 : Window
{
public ObservableCollection<string> Strings { get; set; }
public Window1()
{
InitializeComponent();
Strings = new ObservableCollection<string>(new string[] { "one", "two", "three" });
this.DataContext = this;
}
void HandleButtonClick(object sender, RoutedEventArgs e)
{
string text = "";
for (int i = 0; i < Strings.Count; i++) {
text += Strings[i] + Environment.NewLine;
}
textBlock.Text = text;
}
}
}
Your advice is much appreciated.
As comments by #BrandonKramer and #Peter Duniho show, the solution is that data binding cannot change the object itself to which it is bound, only the properties of that object.
Consequently, I have to create a wrapper class whose property will then be the string I want to manipulate. The code-behind is now
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace ListBoxUpdate
{
public partial class Window1 : Window
{
public ObservableCollection<StringWrapper> Strings { get; set; }
public class StringWrapper
{
public string Content { get; set; }
public StringWrapper(string content)
{
this.Content = content;
}
public static implicit operator StringWrapper(string content)
{
return new Window1.StringWrapper(content);
}
}
public Window1()
{
InitializeComponent();
this.Strings = new ObservableCollection<StringWrapper>(new StringWrapper[] { "one", "two", "three" });
this.DataContext = this;
}
void HandleButtonClick(object sender, RoutedEventArgs e)
{
string text = "";
for (int i = 0; i < Strings.Count; i++) {
text += Strings[i].Content + Environment.NewLine;
}
textBlock.Text = text;
}
}
}
and the XAML has to be modified only at one point
<ListBox
Grid.Column="1"
ItemsSource="{Binding Strings, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Content, Mode=TwoWay}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thank you, #BrandonKramer and #Peter Duniho.
UPDATE: I felt uncomfortable with defining a wrapper just to manipulate objects, and the problem of editing an object itself in a list and not one of its properties seemed to me universal, so I tried to find another solution. I rather decided to hook to the LostFocus event of the TextBox in the ItemTemplate.
In this case, the problem is finding the index in the ListBox from the templated TextBox that is just losing focus. I cannot use the SelectedIndex property of the ListBox as at the time when a LostFocus fires, it is already set to a different ListBoxItem.
I searched quite a bit and have found Dennis Troller’s answer here: WPF ListBoxItems with DataTemplates - How do I reference the CLR Object bound to ListBoxItem from within the DataTemplate? The trick is to get the DataContext of the TextBox losing focus and use the ItemContainerGenerator of the ListBox to first identify the container (with ItemContainerGenerator.ContainerFromItem) then obtain the index in the list (with ItemContainerGenerator.IndexFromContainer).
The code-behind is now
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace ListBoxUpdate
{
public partial class Window1 : Window
{
public ObservableCollection<string> Strings { get; set; }
public Window1()
{
InitializeComponent();
this.Strings = new ObservableCollection<string>(new string[] { "one", "two", "three" });
this.DataContext = this;
}
void HandleButtonClick(object sender, RoutedEventArgs e)
{
string text = "";
for (int i = 0; i < Strings.Count; i++) {
text += Strings[i] + Environment.NewLine;
}
textBlock.Text = text;
}
void HandleTextBoxLostFocus(object sender, RoutedEventArgs e)
{
// https://stackoverflow.com/questions/765984/wpf-listboxitems-with-datatemplates-how-do-i-reference-the-clr-object-bound-to?rq=1, #Dennis Troller's answer.
int index;
object item;
DependencyObject container;
TextBox textBox = sender as TextBox;
if (textBox == null) return;
item = textBox.DataContext;
container = listBox.ItemContainerGenerator.ContainerFromItem(item);
if (container != null) {
index = listBox.ItemContainerGenerator.IndexFromContainer(container);
if (textBox.Text != Strings[index]) {
Strings[index] = textBox.Text;
}
}
}
}
}
with the full XAML as follows (I made the binding to the text one-way):
<Window x:Class="ListBoxUpdate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxUpdate" Height="300" Width="300"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Show items" Click="HandleButtonClick"/>
<TextBlock Grid.Row="1" x:Name="textBlock" />
</Grid>
<ListBox
x:Name="listBox"
Grid.Column="1"
ItemsSource="{Binding Strings}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox
Text="{Binding ., Mode=OneWay}"
LostFocus="HandleTextBoxLostFocus"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>

Binded Propertys' Setter called before View finished Loading

I'm currently developing in WPF (Surface 2.0) and using the MVVM pattern for most parts of my application. I am, unfortunately, currently facing a rather complicated issue I hope you guys can assist me on:
I have a View and a ViewModel that belongs to it. The View contains a two-way binding to a property in the ViewModel:
<pb:PivotBar ItemsSource="{Binding PivotBarEntries}"
SelectedItemIndex="{Binding SelectedPivotItemIndex, Mode=TwoWay}" />
(...)
<local:SomeOtherView />
While the View is first loaded, the setter of SelectedPivotItemIndex is called. This is fine, except that the setter is called before the rest of the view loaded. Since the setter sends messages (via MVVMLight's Messenger) to other viewmodels that are created later in the view, this is a problem - those messages never reach their destination since no receiver is registered for them so far.
public int SelectedPivotItemIndex
{
get
{
return this.selectedPivotItemIndex;
}
set
{
if (value != this.selectedPivotItemIndex)
{
this.selectedPivotItemIndex = value;
this.ReportPropertyChanged("SelectedPivotItemIndex");
(...)
ChangeSomeOtherViewModelProperty msg = new ChangeSomeOtherViewModelProperty { Property = newValueCalculatedBefore };
Messenger.Default.Send<ChangeSomeOtherViewModelProperty>(msg);
}
}
}
The only solution I can think of right now, would be to create a LoadedEventHandler in the ViewModel and call the SelectedPivotItemIndex setter again. I don't really like that, though:
For once, the setter runs again (which creates a rather large collection that is passed to the message). Don't know if it would really impact performance, but still seems unnecessary.
Secondly, it just seems kind of hackish and error prone to me, since every property has to be initialized manually in the loaded event.
Is there any solution to this problem better than just manually calling the setter?
i dont have a tutorial for viewmodel first, but i'm sure the are a lot examples out there. viewmodel first is nothing more then you have the viewmodel instance first and then let wpf create the view(via datatemplate).
let say your mainview should show a view with your PivotBarEntries. so what you do now is to create a pivotbarviewmodel in your mainviewmodel (DI, MEF, new() what ever). your mainviewmodel expose the pivotvw as a property and bind it to a ContentPresenter.Content in your mainview. at least you have to create a DataTemplate for your pivotvw DataType.
<DataTemplate DataType="{x:Type local:PivotViewModel>
<view:MyPivotView/>
</DataTemplate>
thats about viewmodel first, you do not rely on load events on view anymore, because your vm is created first.
of course for your specific problem you just have to be sure that all your components(VM's) which listen to your messenger should be created
your xaml
<ContentPresenter Content="{Binding MyPivotDataVM}" />
<ContentPresenter Content="{Binding MySomeOtherStuffVM}" />
instead of view first
<pb:PivotBar ItemsSource="{Binding PivotBarEntries}"
SelectedItemIndex="{Binding SelectedPivotItemIndex, Mode=TwoWay}" />
(...)
<local:SomeOtherView />
EDIT: very simple example for viewmodel first. ps: i use DI with MEF to create my object path.
app.xaml
<Application x:Class="WpfViewModelFirst.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfViewModelFirst="clr-namespace:WpfViewModelFirst">
<!--StartUp Uri is removed-->
<Application.Resources>
<!--comment these datatemplates and see what happens-->
<DataTemplate DataType="{x:Type WpfViewModelFirst:PivotViewModel}">
<WpfViewModelFirst:PivotView/>
</DataTemplate>
<DataTemplate DataType="{x:Type WpfViewModelFirst:OtherViewModel}">
<WpfViewModelFirst:OtherView/>
</DataTemplate>
<DataTemplate DataType="{x:Type WpfViewModelFirst:OtherChildViewModel}">
<WpfViewModelFirst:OtherChildView/>
</DataTemplate>
</Application.Resources>
</Application>
app.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
//to be fair, sometimes i create the ApplicationRoot(JUST MainWindow with view first, and just the rest with viewmodel first.)
var mainvm = new MainViewModel();
var mainview = new MainWindow {DataContext = mainvm};
this.MainWindow = mainview;
this.MainWindow.Show();
}
}
mainview.xaml
<Window x:Class="WpfViewModelFirst.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MyProp}" Grid.ColumnSpan="2" Grid.Row="0"/>
<ContentPresenter Content="{Binding MyPivot}" Grid.Row="1" Grid.Column="0" />
<ContentPresenter Content="{Binding MyOther}" Grid.Row="1" Grid.Column="1" />
</Grid>
</Window>
mainviewmodel.cs
public class MainViewModel
{
public string MyProp { get; set; }
public PivotViewModel MyPivot { get; set; }
public OtherViewModel MyOther { get; set; }
public MainViewModel()
{
this.MyProp = "Main VM";
this.MyPivot = new PivotViewModel();
this.MyOther = new OtherViewModel();
}
}
PivotViewmodel
public class PivotViewModel
{
public string MyProp { get; set; }
public ObservableCollection<string> MyList { get; set; }
public PivotViewModel()//Dependency here with constructor injection
{
this.MyProp = "Test";
this.MyList = new ObservableCollection<string>(){"Test1", "Test2"};
}
}
OtherViewmodel
public class OtherViewModel
{
public string MyProp { get; set; }
public OtherChildViewModel MyChild { get; set; }
public OtherViewModel()
{
this.MyProp = "Other Viewmodel here";
this.MyChild = new OtherChildViewModel();
}
}
OtherChildViewmodel
public class OtherChildViewModel
{
public string MyProp { get; set; }
public OtherChildViewModel()//Dependency here with constructor injection
{
this.MyProp = "Other Child Viewmodel";
}
}
PivotView
<UserControl x:Class="WpfViewModelFirst.PivotView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding MyProp}" Grid.Row="0"/>
<ListBox ItemsSource="{Binding MyList}" Grid.Row="1"/>
</Grid>
</UserControl>
OtherView
<UserControl x:Class="WpfViewModelFirst.OtherView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding MyProp}" Grid.Row="0" />
<ContentPresenter Content="{Binding MyChild}" Grid.Row="1"/>
</Grid>
</UserControl>
OtherChildView
<UserControl x:Class="WpfViewModelFirst.OtherChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock Text="{Binding MyProp}" />
</Grid>
</UserControl>

Categories

Resources