Custom Tooltip on LineChart (lvChart) - c#

I closely follow step-by-step lvChart Customizing Tooltips in trying to build a custom tooltip for LineChart points. But I get empty tooltip content. Other than using ObservableValue for ChartValues type, my code is near identical to that used in the lvChart tutorial. I am not using any MV**.
Has anyone worked out the tutorial or apply custom tooltip on a LineChart?
Main.xaml
<lvc:CartesianChart.DataTooltip>
<local:MyTooltip />
</lvc:CartesianChart.DataTooltip>
Main.xaml.cs
MyTooltipContents = new ChartValues<MyTooltipContent>();
for (int i = 0; i < MyData.Count(); i++)
{
MyTooltipContents.Add(new MyTooltipContent
{
Name = "No" + i,
Value = "Foo"
});
}
var MyTooltipContentMapper = Mappers.Xy<MyTooltipContent>()
.X((value, index) => index)
.Y(value => 1);
//lets save the mapper globally
Charting.For<MyTooltipContent>(MyTooltipContentMapper);
DataContext = this;
MyTooltip.xaml
<UserControl x:Class="MyNamespace.MyTooltip"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyNamespace"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
d:DataContext="{d:DesignInstance local:MyTooltip}"
Background="#E4555555" Padding="20 10" BorderThickness="2" BorderBrush="#555555">
<ItemsControl ItemsSource="{Binding Data.Points}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type lvc:DataPointViewModel}">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Name"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Value"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{Binding ChartPoint.Instance.(local:MyTooltipContent.Name)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White" />
<TextBlock Grid.Column="2" Text="{Binding ChartPoint.Instance.(local:MyTooltipContent.Value)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
MyToolTip.xaml.cs
public partial class MyTooltip : IChartTooltip
{
private TooltipData _data;
public MyTooltip()
{
InitializeComponent();
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));
}
}
MyTooltipContent.cs
public class MyTooltipContent
{
public string Name { get; set; }
public string Value { get; set; }
}

I copied your example, and it works for me.
You might want to look at your MyTooltipContents declaration on your Main.xaml.cs, which should be a public property:
public ChartValues<MyTooltipContent> MyTooltipContents { get; set; }
I don't know exactly what gave you your line series, but I used this one on my MainWindow.xaml (corresponds to your Main.xaml):
<Window x:Class="MyToolTipExample.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:MyToolTipExample"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<lvc:CartesianChart LegendLocation="Right">
<lvc:CartesianChart.Series>
<lvc:LineSeries Title="Customers" Values="{Binding MyTooltipContents}"></lvc:LineSeries>
</lvc:CartesianChart.Series>
<lvc:CartesianChart.DataTooltip>
<local:MyTooltip/>
</lvc:CartesianChart.DataTooltip>
</lvc:CartesianChart>
</Grid>
</Window>
Note that MyTooltipContents is bound to the LineSeries.
EDIT: included screenshot.
Below, the full code example (.Net Framework 4.8 and LiveCharts 0.9.7):
1 - MainWindow.xaml.cs (the corresponding MainWindow.xaml is above):
namespace MyToolTipExample
{
public partial class MainWindow : Window
{
public ChartValues<MyTooltipContent> MyTooltipContents { get; set; }
public MainWindow()
{
InitializeComponent();
MyTooltipContents = new ChartValues<MyTooltipContent>();
for (int i = 0; i < 50; i++)
{
MyTooltipContents.Add(new MyTooltipContent
{
Name = $"No{i}",
Value = "Foo"
});
}
var MyTooltipContentMapper = Mappers.Xy<MyTooltipContent>()
.X((value, index) => index)
.Y(value => 1);
//lets save the mapper globally
Charting.For<MyTooltipContent>(MyTooltipContentMapper);
DataContext = this;
}
}
}
2 - the UserControl MyTooltip.xaml:
<UserControl x:Class="MyToolTipExample.MyTooltip"
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:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:local="clr-namespace:MyToolTipExample"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:MyTooltip}"
Background="#45555555" Padding="20 10" BorderThickness="2" BorderBrush="#555555">
<ItemsControl ItemsSource="{Binding Data.Points}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type lvc:DataPointViewModel}">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Name"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Value"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{Binding ChartPoint.Instance.(local:MyTooltipContent.Name)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White" />
<TextBlock Grid.Column="2" Text="{Binding ChartPoint.Instance.(local:MyTooltipContent.Value)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
3 - Code-behind the UserControl MyTooltip.xaml.cs:
using LiveCharts;
using LiveCharts.Wpf;
using System.ComponentModel;
using System.Windows.Controls;
namespace MyToolTipExample
{
public partial class MyTooltip : UserControl, IChartTooltip
{
public MyTooltip()
{
InitializeComponent();
DataContext = this;
}
private TooltipData _data;
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));
}
}
}
4 - Data model MyTooltipContent.cs (used by public property ChartValues):
namespace MyToolTipExample
{
public class MyTooltipContent
{
public string Name { get; set; }
public string Value { get; set; }
}
}
5 - Package.config:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="LiveCharts" version="0.9.7" targetFramework="net48" />
<package id="LiveCharts.Wpf" version="0.9.7" targetFramework="net48" />
</packages>
6 - The final solution structure should look something like this:

The application would be crashed when I used the PieChart to customize the ToolTip?
<Window x:Class="MyToolTipExample.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:MyToolTipExample"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>`enter code here`
<lvc:PieChart>
<lvc:PieChart.Series>
<lvc:PieSeries Fill="Green" Stroke="{x:Null}" StrokeThickness="0" />
</lvc:PieChart.Series>
<lvc:PieChart.DataTooltip>
<local:MyTooltip/>
</lvc:PieChart.DataTooltip>
</lvc:PieChart>
</Grid>
</Window>

Related

Dynamically add User Controls based on List in WPF with MVVM pattern

I have two views for my application. Looking at the first View, UpdaterMainView, you can see that I create a Grid with two columns. The left column is split even further, into 5 rows. What I want to do is fill the five rows from a list of objects that are stored in the UpdaterMainViewModel, TaskList. I created a custom User Control called TaskView which is the layout of how I want to display the information of the list, a button, and a textbox.
When I instantiate the UpdaterMainViewModel, my application scans a directory for a few things and creates a list of tasks to complete. There are only five total possible tasks, hence why I created five rows. If the conditions aren't met and Task 2 doesn't need to be run, I don't want to show the UserControl and the one below it, should move up. I don't want to use the code-behind unless it can still meet the guidelines of MVVM.
For testing, I added <local:TaskView Grid.Column="0" Grid.Row="0"/> to the UpdaterMainView and created a default constructor. But, what I need, is to add TaskView for each item in TaskList. To get this to work, I also have to create the UserControls with a different constructor. When you use DataContext, it uses a parameterless constructor. I somehow need to use the constructor with the enum parameter to fill the grid with each appropriate TaskView.
UpdaterMainView:
<UserControl x:Class="POSUpdaterGUI.View.UpdaterMainView"
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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local1:UpdaterMainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<local:TaskView Grid.Column="0" Grid.Row="0"/>
<local:TaskView Grid.Column="0" Grid.Row="1"/>
<local:TaskView Grid.Column="0" Grid.Row="2"/>
<local:TaskView Grid.Column="0" Grid.Row="3"/>
</Grid>
<TextBox Grid.Column="1" Margin="10" TextWrapping="Wrap" Text="{Binding OutputText}"/>
</Grid>
</UserControl>
UpdaterMainViewModel:
using POSUpdaterLibrary;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace POSUpdaterGUI.ViewModel
{
public class UpdaterMainViewModel : INotifyPropertyChanged
{
private string outputText;
public string OutputText
{
get => outputText;
set
{
outputText = value;
NotifyPropertyChanged("OutputText");
}
}
public UpdateTaskList TaskList { get; set; }
public UpdaterMainViewModel()
{
Log("Application Starting.");
TaskList = new UpdateTaskList();
TaskList.LoggerEvent += TaskList_LoggerEvent;
TaskList.GetList(AppContext.BaseDirectory);
}
private void TaskList_LoggerEvent(object sender, LoggerEventArgs e)
{
Log(e.LogString);
}
private void Log(string message)
{
if (OutputText == null)
{
OutputText = DateTime.Now.ToString() + " - " + message;
return;
}
OutputText += "\n" + DateTime.Now.ToString() + " - " + message;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
TaskView:
<UserControl x:Class="POSUpdaterGUI.View.TaskView"
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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="500">
<UserControl.DataContext>
<local1:TaskViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center" Height="50" Width="50" Command="{Binding RunTaskCommand}">
<Image Source="{Binding StatusImage}"/>
</Button>
<Label Grid.Column="1" Content="{Binding TaskName}" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Center"/>
</Grid>
TaskViewModel:
using POSUpdaterLibrary;
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace POSUpdaterGUI.ViewModel
{
class TaskViewModel : INotifyPropertyChanged
{
public string TaskName { get; set; }
public ICommand RunTaskCommand { get; set; }
public string StatusImage { get; set; }
public TaskViewModel()
{
TaskName = "undefined";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
}
public TaskViewModel(UpdaterConstants.TaskType taskType)
{
switch (taskType)
{
case UpdaterConstants.TaskType.KillProcesses:
TaskName = "Processes Killed";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.FileCopy:
TaskName = "Files Copied";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.DatabaseUpdates:
TaskName = "Database Updated";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.COMDLLsRegistered:
TaskName = "COM DLLs Registered";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.FileCleanup:
TaskName = "Files Cleaned";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
default:
break;
}
}
public enum Status
{
START,
COMPLETE,
FAILED,
WAIT
}
internal const string STARTIMAGE = "/data/start.png";
internal const string COMPLETEIMAGE = "/data/complete.png";
internal const string FAILEDIMAGE = "/data/failed.png";
internal const string WAITIMAGE = "/data/wait.png";
public event PropertyChangedEventHandler PropertyChanged;
private void DefaultMethod(object obj)
{
// Shouldn't be shown. Just a hold-over
throw new NotImplementedException();
}
}
}
The UpdaterMainView is the main view of the application and is always showing. To prove this, here is my MainWindow.xaml:
<Window
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:POSUpdaterGUI"
xmlns:View="clr-namespace:POSUpdaterGUI.View" x:Class="POSUpdaterGUI.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<View:UpdaterMainView/>
</Grid>
Thanks #Clemens for pointing me in the right direction! I did not need a separate View. I did need to use Data Templating.
Here is what I ended up with for the UpdaterMainView:
<UserControl x:Class="POSUpdaterGUI.View.UpdaterMainView"
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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local1:UpdaterMainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" Margin="10" ItemsSource="{Binding TaskList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center" Height="50" Width="50" Command="{Binding Command}">
<Image Source="{Binding StatusImage}"/>
</Button>
<Label Grid.Column="1" Content="{Binding TaskName}" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Grid.Column="1" Margin="10" TextWrapping="Wrap" Text="{Binding OutputText}"/>
</Grid>
</UserControl>
Here is what I ended up with for the UpdaterMainViewModel:
using POSUpdaterGUI.Models;
using POSUpdaterLibrary;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace POSUpdaterGUI.ViewModel
{
public class UpdaterMainViewModel : INotifyPropertyChanged
{
private UpdaterLogger _logger;
private string outputText;
public string OutputText
{
get => outputText;
set
{
outputText = value;
NotifyPropertyChanged("OutputText");
}
}
private List<UpdateTaskWrapper> taskList;
public List<UpdateTaskWrapper> TaskList
{
get => taskList;
set
{
taskList = value;
NotifyPropertyChanged("TaskList");
}
}
public UpdaterMainViewModel()
{
_logger = UpdaterLogger.Instance;
_logger.LoggerEvent += Logger_LoggerEvent;
Log("Application Starting.");
TaskList = GetTaskList();
}
private void Logger_LoggerEvent(object sender, LoggerEventArgs e)
{
Log(e.LogString);
}
private List<UpdateTaskWrapper> GetTaskList()
{
UpdateTaskList list = new UpdateTaskList();
var myList = list.GetList(AppContext.BaseDirectory);
// Create one out of the Wrapper class.
List<UpdateTaskWrapper> wrappedList = new List<UpdateTaskWrapper>();
foreach (var task in myList)
{
wrappedList.Add(new UpdateTaskWrapper(task));
}
return wrappedList;
}
private void Log(string message)
{
if (OutputText == null)
{
OutputText = DateTime.Now.ToString() + " - " + message;
return;
}
OutputText += "\n" + DateTime.Now.ToString() + " - " + message;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
The most important parts were that I removed the TaskView, Added Datatemplating (between the tags), and changed the List from a custom UpdaterTaskList to List.

Page doesn't retain UI changes in multi-page WPF/MVVM application

I have a simple multi-page MVVM Light application, my issue is that if I draw something on the screen or change the background color of a button in one of the views/pages then go to a different page and come back, the drawn content is no longer there.
MAIN PAGE CODE:
XAML
<Window x:Class="TwoViews.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"
Title="MVVM Light View Switching"
d:DesignHeight="300"
d:DesignWidth="300"
DataContext="{Binding Main,
Source={StaticResource Locator}}"
ResizeMode="NoResize"
SizeToContent="WidthAndHeight"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Content="{Binding CurrentViewModel}" />
<DockPanel Grid.Row="1" Margin="5">
<Button Width="75"
Height="23"
Command="{Binding SecondViewCommand}"
Content="Second View"
DockPanel.Dock="Right" />
<Button Width="75"
Height="23"
Command="{Binding FirstViewCommand}"
Content="First View"
DockPanel.Dock="Left" />
</DockPanel>
</Grid>
</Window>
ViewModel
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
namespace TwoViews.ViewModels
{
public class MainViewModel : ViewModelBase
{
private ViewModelBase _currentViewModel;
readonly static SecondViewModel _secondViewModel = new SecondViewModel();
readonly static FirstViewModel _firstViewModel = new FirstViewModel();
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
RaisePropertyChanged("CurrentViewModel");
}
}
public ICommand FirstViewCommand { get; private set; }
public ICommand SecondViewCommand { get; private set; }
public MainViewModel()
{
CurrentViewModel = MainViewModel._firstViewModel;
FirstViewCommand = new RelayCommand(() => ExecuteFirstViewCommand());
SecondViewCommand = new RelayCommand(() => ExecuteSecondViewCommand());
}
private void ExecuteFirstViewCommand()
{
CurrentViewModel = MainViewModel._firstViewModel;
}
private void ExecuteSecondViewCommand()
{
CurrentViewModel = MainViewModel._secondViewModel;
}
}
}
Codebehind
namespace TwoViews
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
FIRST PAGE CODE:
XAML
<UserControl x:Class="TwoViews.Views.FirstView"
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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<Label x:Name="label" Content="First Page" HorizontalAlignment="Left" Margin="92,46,0,0" VerticalAlignment="Top"/>
</Grid>
</UserControl>
ViewModel
namespace TwoViews.ViewModels
{
public class FirstViewModel : ViewModelBase
{
public FirstViewModel()
{
}
}
}
Codebehind
namespace TwoViews.Views
{
public partial class FirstView : UserControl
{
public FirstView()
{
InitializeComponent();
}
}
}
SECOND PAGE CODE
XAML
<UserControl x:Class="TwoViews.Views.SecondView"
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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Name="myGrid">
<Grid Name="GridSecondPage" HorizontalAlignment="Left" Height="107" Margin="10,43,0,0" VerticalAlignment="Top" Width="280"/>
<Button x:Name="DrawRectangle" Content="Draw" HorizontalAlignment="Left" Margin="135,203,0,0" VerticalAlignment="Top" Width="75" Click="DrawRectangle_Click"/>
</Grid>
</UserControl>
ViewModel
namespace TwoViews.ViewModels
{
public class SecondViewModel : ViewModelBase
{
}
}
Codebehind
namespace TwoViews.Views
{
public partial class SecondView : UserControl
{
public SecondView()
{
InitializeComponent();
}
private void DrawRectangle_Click(object sender, RoutedEventArgs e)
{
Rectangle myRectangle = new Rectangle();
myRectangle.Name = "myRectangle";
myRectangle.Width = 100;
myRectangle.Height = 100;
myRectangle.Fill = Brushes.Blue;
GridSecondPage.Children.Add(myRectangle);
}
}
}

probleme dealing with several view in mvvm

i'am currently building a software who as several view and i would like to switch from one view to another by clicking on a button on the current view :
of course i have more than 3 views but it's to illustrate the concept. here is my code to go from page 1 to page 2. but i have trouble to go from page 2 to page 3 i don't know what's wrong. thank you for your help.
MainWindow.xaml
<Controls:MetroWindow x:Class="maquette.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:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:maquette"
xmlns:viewModel ="clr-namespace:maquette.ViewModel"
xmlns:view ="clr-namespace:maquette.View"
BorderBrush="{DynamicResource AccentColorBrush}"
BorderThickness="2"
mc:Ignorable="d"
Title="MainWindow">
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:page1ViewModel}">
<local:Page1/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:page2ViewModel}">
<view:Page2/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:page3ViewModel}">
<view:Page3/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:page4ViewModel}">
<view:Page4/>
</DataTemplate>
</Window.Resources>
<Grid>
<Controls:TransitioningContentControl x:Name="pagesControl"
Content="{Binding SelectedViewModel}"
Transition="Left"
/>
</Grid>
</Controls:MetroWindow>
MainWindow.cs
public MainWindow()
{
InitializeComponent();
var viewModel = new NavigationViewModel();
viewModel.SelectedViewModel = new page1ViewModel(viewModel);
this.DataContext = viewModel;
}
NavigationViewModel.cs
public class NavigationViewModel : INotifyPropertyChanged
{
private object selectedViewModel;
public object SelectedViewModel
{
get
{
return selectedViewModel;
}
set
{
selectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
/// <summary>
///
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Page1.xaml
<UserControl x:Class="maquette.Page1"
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:maquette.View"
xmlns:viewModel="clr-namespace:maquette.ViewModel"
d:DesignHeight="300" d:DesignWidth="300"
mc:Ignorable="d"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="Title"
HorizontalAlignment="Center"
TextWrapping="Wrap"
VerticalAlignment="Center"
Width="320"
FontSize="30"
Grid.ColumnSpan="2"
FontFamily="Segoe UI"
FontWeight="Bold">File Control Program</TextBlock>
<Button x:Name="button"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Grid.RowSpan="2"
Width="180"
Height="90"
Style="{StaticResource AccentedSquareButtonStyle}"
Command="{Binding Path=goSettings}"
>
<TextBlock Text="go page 2"
TextWrapping="Wrap"
TextAlignment="Center"/>
</Button>
</Grid>
</UserControl>
page1ViewModel
class page1ViewModel
{
public ICommand goSettings { get; set; }
private readonly NavigationViewModel _navigationViewModel;
public page1ViewModel(NavigationViewModel navigationViewModel)
{
_navigationViewModel = navigationViewModel;
goSettings = new BaseCommand(OpenSettings);
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
private void OpenSettings(object obj)
{
_navigationViewModel.SelectedViewModel = new page2ViewModel();
}
}
page2.xaml
<UserControl x:Class="maquette.View.Page2"
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:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:maquette.View"
xmlns:view ="clr-namespace:maquette.View"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Background="White">
<Grid>
<Button x:Name="button"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Grid.ColumnSpan="2"
Grid.Row="4"
Width="180"
Height="80"
Style="{StaticResource AccentedSquareButtonStyle}"
Command="{Binding Path=goPage3}">
<TextBlock Text="Go page 3"
TextWrapping="Wrap"
TextAlignment="Center"
FontSize="20"
/>
</Button>
</Grid>
</UserControl>
page2.cs
public Page2()
{
InitializeComponent();
var viewModel = new NavigationViewModel();
DataContext = new page2ViewModel(viewModel);
}
page2ViewModel.cs
public class page2ViewModel
{
public ICommand goPage3 { get; set; }
private readonly NavigationViewModel _navigationViewModel;
public page2ViewModel()
{
}
public page2ViewModel(NavigationViewModel navigationViewModel)
{
_navigationViewModel = navigationViewModel;
goPage3 = new BaseCommand(OpenPage3);
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
private void OpenPage3(object obj)
{
_navigationViewModel.SelectedViewModel = new page3ViewModel();
}
}
But as i said the transition between the page 2 and 3 doesn't work. any help ? thank you !
Remove this constructor overload from page2ViewModel:
public page2ViewModel()
{
}
And always inject it with the one and only NavigationViewModel in page1ViewModel:
private void OpenSettings(object obj)
{
_navigationViewModel.SelectedViewModel = new page2ViewModel(_navigationViewModel);
}
Also don't set the DataContext of Page2 explicitly:
public Page2()
{
InitializeComponent();
}
The data templating will make sure that it gets the correct DataContext.
This is for Without using any mvvm framework
Define Data Templates for child viewmodels in Main window viewmodel.
For entire application u have to create static object for your main view model.
Then only views will be changed.
It should be like this...
public partial class App : Application
{
public static MainWindowViewModel mainWindowViewModel;
public App()
{
mainWindowViewModel = new MainWindowViewModel();
}
}
In MainWindow Viewmodel
public class MainWindowViewmodel
{
private object selectedViewModel;
public object SelectedViewModel
{
get
{
return selectedViewModel;
}
set
{
selectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public MainWindowViewmodel()
{
SelectedViewModel = new page1viewmodel();
}
}
In Page1Viewmodel in
class page1ViewModel
{
public ICommand goSettings { get; set; }
private readonly NavigationViewModel _navigationViewModel;
public page1ViewModel(NavigationViewModel navigationViewModel)
{
_navigationViewModel = navigationViewModel;
goSettings = new BaseCommand(OpenSettings);
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
private void OpenSettings(object obj)
{
mainWindowViewModel.SelectedViewModel = new page2ViewModel();
}
}
like this you have to implement.
This will help you.

WPF data binding through UserControls

I would like to bind property from the main window's DataContext, above you can see my UserControls and models. So I want to bind the Model.ID.Label1 and Model.ID.Label2 properties to the main_id/card_1/top and main_id/card_1/bottom controls. I hope it's clear. If I enable the
ref_lbl Label it will shows the "lbl1", the card_2 still working with the hardcoded texts but the card_1 will be blank. What should I modify to fix the binding on card_1?
I have an ID UserControl and it contains another UserControl.
XAML:
<UserControl x:Class="stack.ID"
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:Controls="clr-namespace:stack.Controls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Label Name="ref_lbl" Grid.Row="0" Content="{Binding Label1}" Visibility="Collapsed" />
<Controls:Card x:Name="card_1" Grid.Row="0" TopLabel="{Binding Label1}" BottomLabel="{Binding Label2}" />
<Controls:Card x:Name="card_2" Grid.Row="1" TopLabel="Text 1" BottomLabel="Text 2" />
</Grid>
Code Behind: default, auto generated
Here is the Card UserControl.
XAML:
<UserControl x:Class="stack.Controls.Card"
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="300" d:DesignWidth="300" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" FontSize="20" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding TopLabel}" />
<Label Grid.Row="1" FontSize="20" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding BottomLabel}" />
</Grid>
Code Behind:
namespace stack.Controls
{
public partial class Card : UserControl
{
public static readonly DependencyProperty TopLabelProperty = DependencyProperty.Register("TopLabel", typeof(string), typeof(Card), new PropertyMetadata(default(string)));
public static readonly DependencyProperty BottomLabelProperty = DependencyProperty.Register("BottomLabel", typeof(string), typeof(Card), new PropertyMetadata(default(string)));
public Card()
{
InitializeComponent();
}
public string TopLabel
{
get
{
return (string)GetValue(TopLabelProperty);
}
set
{
SetValue(TopLabelProperty, value);
}
}
public string BottomLabel
{
get
{
return (string)GetValue(BottomLabelProperty);
}
set
{
SetValue(BottomLabelProperty, value);
}
}
}
}
And here is my main window.
XAML:
<Window x:Class="stack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:stack"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Model />
</Window.DataContext>
<Grid>
<local:ID x:Name="main_id" DataContext="{Binding ID}" />
</Grid>
Code Behind: default, auto generated
And I also have 2 models.
namespace stack
{
public class IDModel
{
private string label1 = "lbl1";
private string label2 = "lbl2";
public string Label1
{
get
{
return label1;
}
set
{
label1 = value;
}
}
public string Label2
{
get
{
return label2;
}
set
{
label2 = value;
}
}
}
public class Model
{
private IDModel id = new IDModel();
public IDModel ID
{
get
{
return id;
}
set
{
id = value;
}
}
}
}
Remove
DataContext="{Binding RelativeSource={RelativeSource Self}}"
from the Card's XAML.
It prevents inheriting the DataContext from its parent ID control, which is necessary when you write
<Controls:Card ... TopLabel="{Binding Label1}" />
Instead write the Content bindings in Card's XAML like this:
<Label ... Content="{Binding TopLabel,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />

How to bind a property in the view model of a usercontrol to a property in another usercontrol

I am currently working within a WPF user control using MVVM. My MainWindow.xaml looks like below.
MainWindow.xaml
<Window.Resources>
<ObjectDataProvider x:Key="TabsList" ObjectType="{x:Type local:MainWindowModel}" MethodName="GetTabs"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Source={StaticResource TabsList}}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=TabName}" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding Source={StaticResource TabsList}, Path=MyUserControl}"/>
</Grid>
Data provider class is as below
public class MainWindowModel
{
public List<TabInfo> GetTabs()
{
return new List<TabInfo>()
{
new TabInfo() { TabName="Tab1", MyUserControl = new UserControl1()},
new TabInfo() { TabName="Tab2", MyUserControl = new UserControl2()}
};
}
}
public class TabInfo
{
public string TabName { get; set; }
public UserControl MyUserControl { get; set; }
}
And now I have two usercontrols UserControl1 and UserControl2 each has a text box control. I would like to update the Text property of the textbox control in the UserControl1 whenever the Text property of the Textbox control in the UserControl2 is updated. To do this, I tried as below.
UserControl1
<UserControl x:Class="MoreOnBinding2.UserControl1"
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="300" d:DesignWidth="300">
<Grid>
<TextBox Width="200" Height="20" Text="{Binding Path=UserControl1Text}"/>
</Grid>
UserControl1ViewModel
public class UserControl1VM : ViewModelBase
{
public UserControl1VM()
{
this.UserControl1Text = "10";
}
private string userControl1Text;
public string UserControl1Text
{
get { return userControl1Text; }
set
{
if (userControl1Text != value)
{
userControl1Text = value;
RaisePropertyChanged(() => UserControl1Text);
}
}
}
}
UserControl2
<UserControl x:Class="MoreOnBinding2.UserControl2"
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:MoreOnBinding2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Width="200" Height="20"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl1}},
UpdateSourceTrigger=PropertyChanged, Path=UserControl1Text}" />
</Grid>
But it is not working. It seems there is a problem in the RelativeSource tag in the UserControl2. Can someone help on this.
You could use a Dependency Property on your user controls this would give you a bind able property. see link
EDIT 1
Just had another look at your class setup. If you are following the MVVM approach I don't think you should have a reference to your user control in your view model
View Model should be more Like
public Class
{
ObservableCollection<TabInfo> _Tabs = new ObservableCollection<TabInfo>();
public ObservableCollection<TabInfo> Tabs
{
get{return _Tabs;}
set {_Tabs = value;}//Need to implement INotifyPropertyChanged [Link][2]
}
public TabInfo SelectedTab {get;set;}
}
public class TabInfo
{
public string TabName { get; set; }
public UserControlViewModel MyUserControlViewModel{ get; set; }
}
Then in your View
<Window.DataContext>
<vm:MainWindowModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Tabs}" IsSynchronizedWithCurrentItem="True" SelectedValue="{Binding SelectedTab}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=TabName}" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding SelectedTab.MyUserControlViewModel"/>
</Grid>
This should give you a list of Tabs and then when one is selected it will set the content of the ContentControl to the MyUserControlViewModel.
You would then need to use some sort of template selector to control the ContentTemplate to load different UserControls into the ContentControl
I haven't tested the code works and you would need to implement the INotifyPropertyChanged on all the public properties so the bindings update as the properties value is changed.
Hope that helps a bit.

Categories