ItemsControl DataTemplate not binding to underlining data - c#

I am having a very difficult time trying to get this to two-way bind to the item in the collection. The strange thing here is that the control shows the label but when I type anything in the text box it doesn't set the underlining value. Can someone please tell me what I'm doing wrong here.
<ItemsControl Grid.Row="0" Grid.RowSpan="2" ItemsSource="{Binding QueryObject.RequiredParameters}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type queryModels:QueryObjectParameter}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{Binding Label}"></Label>
<TextBox Grid.Row="1" Text="{Binding Value, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I've tried these different types.
{Binding Path=Value, RelativeSource={RelativeSource Self} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
{Binding XPath=DataContext.Value, RelativeSource={RelativeSource Self} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
{Binding XPath=Value, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
{Binding Path=Value, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
Thank you for your help!
Edit:
I have been asked to add a better example to this so I created a very easy example. Note: the underlining model is called, but it doesn't set the model in the ViewModel.
public class MainWindowViewModel:INotifyPropertyChanged
{
public MainWindowViewModel()
{
PersonQuery = new PersonQuery();
Command = new DelegateCommand(CommandAction);
}
private void CommandAction()
{
MessageBox.Show(PersonQuery.Parameters.First().ToString());
}
public DelegateCommand Command { get; set; }
public PersonQuery PersonQuery { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Parameter
{
public Parameter(string label)
{
Label = label;
}
public string Label { get; set; }
public object Value { get; set; }
public override string ToString()
{
return $"{Label}:{Value}";
}
}
public class PersonQuery
{
public Parameter[] Parameters => new[] {new Parameter("Test"),};
}
XAML:
<Button Content="Run" Command="{Binding Command}"></Button>
<ItemsControl Grid.Row="1" ItemsSource="{Binding PersonQuery.Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Parameter}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Content="{Binding Label}"></Label>
<TextBox Grid.Row="1" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Here's a simplified example where an ItemsControl is used and property update works both ways.
Simple Data class:
public class Data
{
public int Id { get; set; }
public string Name { get; set; }
}
Here I implemented INotifyPropertyChanged on my MainWindow for convenience, but you should really use a ViewModel and do this there.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Data> _dataList = null;
public ObservableCollection<Data> DataList
{
get { return _dataList; }
set
{
_dataList = value;
OnPropertyChanged("DataList");
}
}
public MainWindow()
{
InitializeComponent();
DataList = new ObservableCollection<Data>
{
new Data() { Id = 1, Name = "Dan" },
new Data() { Id = 2, Name = "Julie" }
};
DataContext = this;
}
}
The XAML is super simple:
<ItemsControl ItemsSource="{Binding DataList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Id}"/>
<TextBox Grid.Column="1" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
NOTE:
Check in your code if you have:
Implemented INotifyPropertyChanged
Use an ObservableCollection for your list.

Related

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.

How to get access to the MainWindow viewmodel from Page?

I'm trying to understand binding system in WPF. In my example i need to get access to MainWindow viewmodel from Page in XAML.
I have one solution to implement this. But i want to know more different ways
MainWindow.xaml
<Window x:Class="FunAnkiWPF.MainWindow"
...omitted for brevity
Height="450" Width="800"
DataContext="{Binding ViewModel, RelativeSource={RelativeSource
Self}}">
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
ViewModel = new MainWindowViewModel(this);
InitializeComponent();
}
}
StartPage.xaml (usual page)
StartPage.xaml.cs (One solution that works)
public partial class StartPage : Page
{
public StartPage()
{
InitializeComponent();
DataContext = App.Current.MainWindow.DataContext;
}
}
How to get a direct access to the MainWindow ViewModel property (in XAML and in codebehind)?
How to get access to another datacontext in XAML (like in my StartPage codebehind)?
The short answer to binding from a child control to a property in the parent window's datacontext is relativesource eg:
<TextBlock Text="{Binding Path=DataContext.MainWinVMString,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Here's an example to give you the flavour of what I am suggesting.
The MainWindow markup is a bit quick n dirty.
I would put datatemplates in a resource dictionary merged by app.xaml.
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginUC/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:UserViewModel}">
<local:UserUC/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl ItemsSource="{Binding NavigationViewModelTypes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding VMType}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentControl Grid.Column="1"
Content="{Binding CurrentViewModel}"
/>
</Grid>
Viewmodel for that:
public class MainWindowViewModel : INotifyPropertyChanged
{
public string MainWinVMString { get; set; } = "Hello from MainWindoViewModel";
public ObservableCollection<TypeAndDisplay> NavigationViewModelTypes { get; set; } = new ObservableCollection<TypeAndDisplay>
(
new List<TypeAndDisplay>
{
new TypeAndDisplay{ Name="Log In", VMType= typeof(LoginViewModel) },
new TypeAndDisplay{ Name="User", VMType= typeof(UserViewModel) }
}
);
private object currentViewModel;
public object CurrentViewModel
{
get { return currentViewModel; }
set { currentViewModel = value; RaisePropertyChanged(); }
}
private RelayCommand<Type> navigateCommand;
public RelayCommand<Type> NavigateCommand
{
get
{
return navigateCommand
?? (navigateCommand = new RelayCommand<Type>(
vmType =>
{
CurrentViewModel = null;
CurrentViewModel = Activator.CreateInstance(vmType);
}));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Relaycommand is from nuget package mvvmlightlibs.
UserUC:
<Grid Background="pink">
<TextBlock Text="This is the User module Control"
VerticalAlignment="Top"
/>
<TextBlock Text="{Binding Path=DataContext.MainWinVMString, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
VerticalAlignment="Bottom"
/>
</Grid>
Full working sample:
https://1drv.ms/u/s!AmPvL3r385QhgqIZUul-ppiIHZ9uyA

Binding across tab items with button

I have a wpf with multiple tabs. The application is a step by step learning tool for students. I want the tabs to be locked initially. When the user has entered the correct data in a tab, the next is enabled after pressing the proceed button.
I tried binding the 'isEnabled' property of tabitem and setting it true when the button is pressed in the View model. It doesn't work.
Here's some snippets of my code:
XAML:
<TabItem Header="Step 1"><my:Step1 Loaded="Step1_Loaded" /></TabItem>
<TabItem Header="Step 2"><my:Step2 Loaded="Step2_Loaded" /></TabItem>
<TabItem IsEnabled="{Binding step3Enabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Step 3"><my:Step3 Loaded="Step3_Loaded" /></TabItem>
Code-behind for the button:
private void proceed2_Click(object sender, RoutedEventArgs e)
{
var vm = DataContext as ViewModel.ValuesCheck;
if (vm != null)
vm.Step2check();
}
View Model:
class ViewModel:INotifyPropertyChanged
{
bool _step3enabled = false;
public bool step3Enabled
{
get { return _step3enabled; }
set { _step3enabled = value; OnPropertyChanged("step3Enabled"); }
}
public void Step2check()
{
this.step3Enabled = true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
}
}
As I can understand, you need some navigation control that presents its content based on some condition. I can suggest you to use a listbox control and manage its ListBoxItems content visibility or something like this.
Xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding Controls}" SelectedItem="{Binding CurrentControlContent}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Visibility="{Binding Path = TabIsEnabled, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource Converter}}"
BorderBrush="Tomato" BorderThickness="1" IsEnabled="{Binding Path = TabIsEnabled, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<Button Width="100" Height="30" IsEnabled="{Binding Path = TabIsEnabled, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
IsHitTestVisible="False" Content="{Binding Path = Content, UpdateSourceTrigger=PropertyChanged}"></Button>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<ContentControl Content="{Binding CurrentControlContent.Content}"></ContentControl>
<Button Grid.Row="1" Content="Check" Command="{Binding CheckCommand}"></Button>
</Grid>
ViewModel
private TabAndControlModel _selectedTabControlModel;
private ICommand _checkCommand;
private TabAndControlModel _currentControlContent;
public ObservableCollection<TabAndControlModel> Controls { get; set; }
public TabAndControlModel CurrentControlContent
{
get { return _currentControlContent; }
set
{
_currentControlContent = value;
OnPropertyChanged();
}
}
public ICommand CheckCommand
{
get { return _checkCommand ?? (_checkCommand = new RelayCommand(Check)); }
}
private void Check()
{
var index = Controls.IndexOf(CurrentControlContent);
var nextIndex = index + 1;
if(nextIndex >= Controls.Count) return;
CurrentControlContent = Controls[nextIndex];
CurrentControlContent.TabIsEnabled = true;
}
Model
private bool _tabIsEnabled;
private string _content;
public bool TabIsEnabled
{
get { return _tabIsEnabled; }
set
{
_tabIsEnabled = value;
OnPropertyChanged();
}
}
public string Content
{
get { return _content; }
set
{
_content = value;
OnPropertyChanged();
}
}
View on running:
And you have to template and style all that stuff...
Regards

C# - Fill TextBox from WPF DataGrid

I have a WPF window with some text boxes and a DataGrid. The DataGrid gets filled with Data, but I need that, when the user clicks a cell in that DataGrid, the program detects the line and re-fills the text boxes with the data in that line.
For example, there is an ID, Name and BirthDate textbox. When the user clicks any cell in a given line, the text boxes ID, Name and BirthDate's values must become the values of their respective columns (ID, Name, BirthDate) in the line selected.
I've looked around for an answer to this, but I only seem to find answers relating to WinForms, and the DataGrid in WinForms and in WPF work quite differently, code-wise.
You can simply use Binding using the ElementName property to do this for you... no code behind needed:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<DataGrid Name="DataGrid" ItemsSource="{Binding YourDataCollection}" />
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding SelectedItem.Id,
ElementName=DataGrid}" />
<TextBlock Grid.Row="1" Text="{Binding SelectedItem.Name,
ElementName=DataGrid}" />
<TextBlock Grid.Row="2" Text="{Binding SelectedItem.BirthDate,
ElementName=DataGrid}" />
</Grid>
</Grid>
Here I made a tiny illustration following your original example:
//XAML
<Grid>
<DataGrid ItemsSource="{Binding Persons}" Margin="0,0,136,0" SelectedItem="{Binding SelectedPerson}"></DataGrid>
<Label Content="{Binding SelectedPerson.Id}" HorizontalAlignment="Left" Margin="400,35,0,0" VerticalAlignment="Top" Width="90" Height="26"/>
<Label Content="{Binding SelectedPerson.Name}" HorizontalAlignment="Left" Margin="400,97,0,0" VerticalAlignment="Top" Width="90" Height="24"/>
<Label Content="{Binding SelectedPerson.BirthDate}" HorizontalAlignment="Left" Margin="400,66,0,0" VerticalAlignment="Top" Width="90" Height="26"/>
</Grid>
//.cs
public MainWindow()
{
InitializeComponent();
DataContext = new PersonViewModel();
}
//ViewModel
public class PersonViewModel : INotifyPropertyChanged
{
private Person _selectedPerson;
public List<Person> Persons { get; set; }
public Person SelectedPerson
{
get { return _selectedPerson; }
set { _selectedPerson = value; OnPropertyChanged("SelectedPerson"); }
}
public PersonViewModel()
{
Persons = new List<Person>
{
new Person(){Id = 1,BirthDate = DateTime.Now.AddYears(-30),Name = "Mark"},
new Person(){Id = 2,BirthDate = DateTime.Now.AddYears(-40), Name = "Sophy"},
new Person(){Id = 3,BirthDate = DateTime.Now.AddYears(-50), Name = "Bryan"},
};
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
// Model
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }
}

WPF - MVVM : How to Check/Uncheck all Items in a ListView

I have the following requirements:
Window will show a ListView with multiple items.
User should be able to check (Checkbox) any item.
a) If one item, all items should be unchecked and disabled.
b) If checked item is unchecked, than all items should be enabled.
As of now, I have the following incomplete code.
MainWindow XAML:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="520.149" Width="732.463">
<Window.Resources>
<ResourceDictionary Source="MainWindowResource.xaml" />
</Window.Resources>
<Grid>
<ListView x:Name="myListBox" ItemTemplate="{StaticResource OfferingTemplate}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
</Window>
DataTemplete for ListView:
<DataTemplate x:Key="OfferingTemplate">
<StackPanel>
<Grid IsEnabled="{Binding IsEnabled}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8"></ColumnDefinition>
<ColumnDefinition Width="120"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<Rectangle Grid.Column="0" Grid.RowSpan="3" Fill="#F4CA16" />
<Label
Grid.Column="1"
Grid.Row="0"
Content="{Binding Title}"
FontSize="18" FontWeight="Bold"
Margin="0,0,0,0" />
<TextBlock
Grid.Column="1"
Grid.Row="1"
FontSize="10"
Text="{Binding Description}"
Foreground="Black"
TextWrapping="WrapWithOverflow"
Margin="5,0,0,0" />
<CheckBox
Grid.Column="1"
Grid.Row="2"
FontSize="14"
IsChecked="{Binding IsSelected}"
VerticalAlignment="Bottom"
Margin="5,0,0,0">
<TextBlock Text="Select" Margin="0,-2,0,0"/>
</CheckBox>
</Grid>
</StackPanel>
</DataTemplate>
Model:
class MyModel
{
public string Title { get; set; }
public string Description { get; set; }
public bool IsSelected { get; set; }
public bool IsEnabled { get; set; }
}
ViewModel:
class MyViewModel : INotifyPropertyChanged
{
private MyModel offering;
public MyViewModel()
{
offering = new MyModel();
}
public int ID { get; set; }
public string Title
{
get { return offering.Title; }
set
{
offering.Title = value;
RaisePropertyChanged("Title");
}
}
public string Description
{
get { return offering.Description; }
set
{
offering.Description = value;
RaisePropertyChanged("Description");
}
}
public bool IsSelected
{
get { return offering.IsSelected; }
set
{
offering.IsSelected = value;
RaisePropertyChanged("IsSelected");
}
}
public bool IsEnabled
{
get { return offering.IsEnabled; }
set
{
offering.IsEnabled = value;
RaisePropertyChanged("IsEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This is an interesting question. Since the action you want applies to all items in the list, this logic should in list class level. Your MyViewModel class is fine. You need add some logic in your list class and XAML but thanks to Prism, it is quite easy.
The list class (not shown in your post) Contains:
public ObservableCollection<MyViewModel> MyItems { get; set; } //Binding to ItemsSource
private ICommand _selectCommand;
public ICommand SelectCommand
{
get { return _selectCommand ?? (_selectCommand = new DelegateCommand<MyViewModel>(DoSelect)); }
}
private void DoSelect(MyViewModel myViewModel)
{
foreach(var item in MyItems)
if (item != myViewModel)
{
item.IsSelected = false;
item.IsEnabled = false;
}
}
private ICommand _unselectCommand;
public ICommand UnselectCommand
{
get { return _unselectCommand ?? (_unselectCommand = new DelegateCommand<MyViewModel>(DoUnselect)); }
}
private void DoUnselect(MyViewModel myViewModel)
{
foreach (var item in MyItems)
if (item != myViewModel)
{
item.IsEnabled = true;
}
}
There are two commands, one for selecting and the other for unselecting. The magic is on XAML:
<ListView ItemsSource="{Binding Path=MyItems}" x:Name="listView">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected}" IsEnabled="{Binding Path=IsEnabled}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding ElementName=listView, Path=DataContext.SelectCommand}"
CommandParameter="{Binding}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding ElementName=listView, Path=DataContext.UnselectCommand}"
CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Using Prism's triggers, you can map CheckBox's Checked and Unchecked event to your list view model's commands and passing the item view model as parameter.
It is working perfectly but one thing is annoying, that setting item's IsSelected is separate. When you check a CheckBox, the item behind is set to true through DataBinding but all others are set through parent view model. If your post is all your requirement, you can remove IsChecked binding and put the logic of setting one IsSelected inside list view model, which looks clenaer and easier to write test code.

Categories

Resources