Unable to programmatically read an updated user control property from within MainWindowViewModel - c#

I've following code in my WPF app.I'm dynamically rendering the user control in MainWindow using ContentPresenter control on change of SelectedAccountType combobox value.
I enter some value in MyProperty textbox in UI and click on Save button on MainWindow.But I dont see this value in the MainWindowViewModel.cs in the Save method(i.e. on click of Save button).
All my ViewModels extend this abstract class:
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
What am I missing here please?
Thanks.
Here's my code:
MainWindow.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:MainViewModel="clr-namespace:Test.ViewModel"
xmlns:ViewModel="clr-namespace:Test.ViewModel.AccountTypes"
xmlns:View="clr-namespace:Test.View" x:Class="Test.MainWindow"
xmlns:Views="clr-namespace:Test.View.AccountTypes"
xmlns:v="clr-namespace:Test.View.AccountTypes"
xmlns:vm="clr-namespace:Test.ViewModel.AccountTypes"
Title="{Binding DisplayName, Mode=OneWay}" ResizeMode="CanResize" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<MainViewModel:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal" Height="28" Width="auto" Margin="5,0,0,0">
<ComboBox Width="360" Margin="1,0" ItemsSource="{Binding AccountTypes}" DisplayMemberPath="Code" SelectedValuePath="ID" SelectedItem="{Binding SelectedAccountType,
Mode=TwoWay}" TabIndex="0" />
</StackPanel>
<ContentPresenter Content="{Binding CurrentViewModel}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type ViewModel:AC1ViewModel}">
<Views:AC1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:AC2ViewModel}">
<Views:AC2View/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
MainWindowViewModel.cs
public object CurrentViewModel
{
get
{
return m_currentViewModel;
}
set
{
m_currentViewModel = value;
OnPropertyChanged("CurrentViewModel");
}
}
public AccountType SelectedAccountType
{
get
{
return m_selectedSearchAccountType;
}
set
{
m_selectedSearchAccountType = value;
if (SelectedAccountType.Code == "AC1")
{
CurrentViewModel = new AC1ViewModel();
}
else if (SelectedAccountType.Code == "AC2")
{
CurrentViewModel = new AC2ViewModel();
}
}
}
public void Save()
{
Type sourceType = CurrentViewModel.GetType();
PropertyInfo[] sourcePI = sourceType.GetProperties();
Type destinationType = securityDetails.GetType();
PropertyInfo[] destinationPI = destinationType.GetProperties();
string propertyName = string.Empty;
object propertyValue = null;
foreach (var pinfo in sourcePI)
{
propertyName = pinfo.Name.Trim();
propertyValue = pinfo.GetValue(CurrentViewModel)
}
}
AC1View.xaml:
<TextBox HorizontalAlignment="Left" Height="23" Margin="1,1,0,0" VerticalAlignment="Top" Width="230" TabIndex="1" Text="{Binding MyProperty,UpdateSourceTrigger=PropertyChanged}" />
AC1ViewModel.cs
public class AC1ViewModel
{
private string m_myProperty = "";
public AC1ViewModel()
{
}
public string MyProperty
{
get
{
return m_myProperty;
}
set
{
m_myProperty = value;
}
}
}

Related

Selecting a data template based on type in UWP

Given these types
public class TestTypeBase
{
public string Name { get; set; }
}
public class TestTypeToggle : TestTypeBase
{
}
public class TestType : TestTypeBase
{
public bool Enabled { get; set; } = false;
}
this data context
public class vm
{
public ObservableCollection<TestTypeBase> TestTypes { get; } = new ObservableCollection<TestTypeBase> { new TestTypeToggle { Name = "Don't Test" }, new TestTypeToggle { Name = "Always Test" }, new TestType { Name = "qwert", Enabled = true }, new TestType { Name = "qwert", Enabled = true } };
}
(xaml)
<Page.DataContext>
<local:vm />
</Page.DataContext>
and this view
<ComboBox Width="120" ItemsSource="{Binding TestTypes}">
<ComboBox.Resources>
<DataTemplate x:Key="a" x:DataType="local:TestType">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<CheckBox IsChecked="{Binding Enabled}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="b" x:DataType="local:TestTypeToggle">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
i was hoping that the ItemTemplate would be selected based on the item types but all i get are the type names as string.
This solution seems promising but i cannot figure how to give the type hint.
(I'm basically having the same problems as in this question but in a UWP context)
Is this possible or do i have to use a ItemTemplateSelector?
Yes, UWP is different from WPF. You have to use a ItemTemplateSelector in UWP.
Here are the official documents for your reference.
Page.xaml
<Page.Resources>
<DataTemplate x:Key="NormalItemTemplate" x:DataType="x:Int32">
<Button HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{ThemeResource SystemChromeLowColor}">
<TextBlock Text="{x:Bind}" />
</Button>
</DataTemplate>
<DataTemplate x:Key="AccentItemTemplate" x:DataType="x:Int32">
<Button HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{ThemeResource SystemAccentColor}">
<TextBlock Text="{x:Bind}" />
</Button>
</DataTemplate>
<local:MyDataTemplateSelector x:Key="MyDataTemplateSelector"
Normal="{StaticResource NormalItemTemplate}"
Accent="{StaticResource AccentItemTemplate}"/>
</Page.Resources>
<Grid>
<ListView x:Name = "TestListView"
ItemsSource = "{x:Bind NumbersList}"
ItemTemplateSelector = "{StaticResource MyDataTemplateSelector}">
</ListView>
</Grid>
Page.xaml.cs
public sealed partial class MainPage : Page
{
public ObservableCollection<int> NumbersList = new ObservableCollection<int>();
public MainPage()
{
this.InitializeComponent();
NumbersList.Add(1);
NumbersList.Add(2);
NumbersList.Add(3);
}
}
public class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate Normal { get; set; }
public DataTemplate Accent { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if ((int)item % 2 == 0)
{
return Normal;
}
else
{
return Accent;
}
}
}
Edit
According to this document,
If your ItemsControl.ItemsPanel is an ItemsStackPanel or
ItemsWrapGrid, provide an override for the SelectTemplateCore(Object)
method. If the ItemsPanel is a different panel, such as
VirtualizingStackPanel or WrapGrid, provide an override for the
SelectTemplateCore(Object, DependencyObject) method.
So you need override the SelectTemplateCore(Object, DependencyObject) method in your DataTemplateSelector.
Page.xaml
<Page.Resources>
<DataTemplate x:Key="NormalItemTemplate" x:DataType="x:Int32">
<TextBox Text="{x:Bind}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{ThemeResource SystemChromeLowColor}" />
</DataTemplate>
<DataTemplate x:Key="AccentItemTemplate" x:DataType="x:Int32">
<TextBox Text="{x:Bind}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{ThemeResource SystemAccentColor}" />
</DataTemplate>
<local:MyDataTemplateSelector x:Key="MyDataTemplateSelector"
Normal="{StaticResource NormalItemTemplate}"
Accent="{StaticResource AccentItemTemplate}"/>
</Page.Resources>
<Grid>
<ComboBox x:Name="Testcombox"
ItemsSource="{x:Bind NumbersList}"
ItemTemplateSelector = "{StaticResource MyDataTemplateSelector}">
</ComboBox>
</Grid>
Page.xaml.cs
public sealed partial class MainPage : Page
{
public ObservableCollection<int> NumbersList = new ObservableCollection<int>();
public MainPage()
{
this.InitializeComponent();
NumbersList.Add(1);
NumbersList.Add(2);
NumbersList.Add(3);
}
}
public class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate Normal { get; set; }
public DataTemplate Accent { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if ((int)item % 2 == 0)
{
return Normal;
}
else
{
return Accent;
}
}
}

MVVM Property Change in Behavior<TreeView>

I cannot get a change in the UI (TextBox) when selecting a tree item. When I select a tree item, the property and the text should change. But this does not happen. Property changes, but text remains the same. At first I tried to cause a property change in MainViewModel, but ran into the same problem: the property changes but the UI remains unchanged.
public class BindableSelectedItemBehavior : Behavior<TreeView>, INotifyPropertyChanged
{
MainViewModel vm = new MainViewModel();
private string _message;
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged("Message");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(nameof(SelectedItem), typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
string name = GetName((Item)SelectedItem);
Message = "edgddgdgdg";
}
public string GetName(Item item)
{
string circ = item.Name;
return circ;
}
}
}
<UserControl x:Class="WpfApp3.TreeViewTextChange"
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:WpfApp3"
xmlns:vm="clr-namespace:WpfApp3.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:BindableSelectedItemBehavior/>
</UserControl.DataContext>
<Grid>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Path=Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Background="Aqua" Width="200" Height="50"/>
</Grid>
</UserControl>
Used a debugger - property changes but does not change textbox in UserControl.
My MainViewModel
class MainViewModel : BindableBase
{
MainModel mainModel = new MainModel();
private ICollectionView root;
public MainViewModel()
{
OpenSth = new DelegateCommand(DisplayItemTree);
ShowCheck = new DelegateCommand(ShowCheckedItemsCommand);
Sth = new DelegateCommand(Changetext);
TextChengedCommand = new DelegateCommand<object>(textChange);
}
public ICommand OpenSth { get; }
public ICommand ShowCheck { get; }
public ICommand Sth { get; }
public ICommand TextChengedCommand { get; }
private void textChange(object obj)
{
var str = obj as string;
}
public void DisplayItemTree()
{
var itemTree = mainModel.GetItemsTree();
Root = CollectionViewSource.GetDefaultView(itemTree);
Root.Filter = new Predicate<object>((item) =>
{
var realItem = (Item)item;
return true;
});
Root.Refresh();
}
public ICollectionView Root
{
get => root;
set => SetProperty(ref root, value);
}
public void ShowCheckedItemsCommand()
{
var str = new StringBuilder();
List<Item> items = mainModel.FindCheckedItem();
if (items.Count == 0)
{
str.Append("No selected items");
}
else
{
foreach (var item in items)
{
str.Append(item.Name);
}
}
MessageBox.Show(str.ToString());
}
private string _name;
public string TextText
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("TextText");
}
}
public void Changetext()
{
TextText = "dggg";
}
}
}
And xaml code
<Window x:Class="WpfApp3.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:WpfApp3"
xmlns:beh="clr-namespace:WpfApp3.ViewModel"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ii="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="http://www.codeplex.com/prism"
mc:Ignorable="d"
xmlns:vm="clr-namespace:WpfApp3.ViewModel"
xmlns:bh="clr-namespace:WpfApp3.ViewModel"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">
<TextBox x:Name="searchBox" DockPanel.Dock="Top" Height="30" VerticalAlignment="Top" BorderBrush="Black" BorderThickness="1" Text="{Binding SearchText}">
<ii:Interaction.Triggers>
<ii:EventTrigger EventName="TextChenged">
<ii:InvokeCommandAction Command="{Binding TextChengedCommand}" CommandParameter="{Binding ElementName=searchBox, Path=Text}"/>
</ii:EventTrigger>
</ii:Interaction.Triggers>
</TextBox>
<DockPanel DockPanel.Dock="Bottom">
<Button Content="Get TreeView" Command="{Binding OpenSth}" Width="100"/>
<Button Content="Show Check Item" Command="{Binding ShowCheck}" Width="100"/>
</DockPanel>
<TreeView BorderBrush="Black" BorderThickness="1" ItemsSource="{Binding Root}">
<i:Interaction.Behaviors>
<beh:BindableSelectedItemBehavior SelectedItem="{Binding SelectedNode, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Sub}">
<DockPanel>
<CheckBox IsChecked="{Binding CheckedItem}" Margin="0 8 0 8" VerticalAlignment="Center" DockPanel.Dock="Left"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" DockPanel.Dock="Right"/>
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DockPanel>
<DataGrid AutoGenerateColumns="False" Height="210" HorizontalContentAlignment="Left" Name="DataGrid" VerticalAlignment="Top" Grid.Column="2" Grid.Row="0" Margin="0,0,0,0" ItemsSource="{Binding ListOfCircuets}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Length}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding dU}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding Cable}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding I}" MinWidth="50"/>
</DataGrid.Columns>
</DataGrid>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding TextText, Mode=TwoWay}" Background="Aqua" Width="200" Height="50"/>
<Button Content="Show Check Item" Command="{Binding Sth}" Width="100"/>
<local:TreeViewTextChange Grid.Row="1" Grid.Column="2"></local:TreeViewTextChange>
</Grid>
</Window>

Dynamic ViewModel's bound values becomes null in WPF

I followed few online tutorials to switch between dynamic views from a ListView.
In my MainWindow, I have a ListView in the left pane with ItemsSource of list of sub ViewModels. (Each sub ViewModel implements an Interface)
Each ViewModel have its own View as a DataTemplate.
I'm calling the GenerateReport() method of the selected view from the MainWindow. But the values of selected views becomes null.
Download my source from Github.
Steps to reproduce the issue:
Run the application and type text in the Students Report's Id and Name. (The breakpoints in StudentReportViewModels's properties are properly hitting and values updated.)
Then click Generate Report button. The StudentReportViewModels's properties values becomes null.
How to fix the issue? Please help.
Source:
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type vm:StudentsReportViewModel}">
<view:StudentsReport/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MarksReportViewModel}">
<view:MarksReport />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding Reports}" SelectedItem="{Binding SelectedReport, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<GridSplitter Grid.Row="1" Grid.Column="1" Width="5" ResizeBehavior="PreviousAndNext" HorizontalAlignment="Stretch"/>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ContentControl Content="{Binding SelectedReport.ViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ScrollViewer>
<Button Content="Generate Report" Grid.Row="2" Margin="5" HorizontalAlignment="Right" Command="{Binding GenerateReportCommand}"/>
</Grid>
</Grid>
MainWindowViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ICommand _generateReportCommand;
private Report selectedReport;
public Report SelectedReport
{
get
{
return this.selectedReport;
}
set
{
if (value != this.selectedReport)
{
this.selectedReport = value;
NotifyPropertyChanged();
}
}
}
public List<Report> Reports { get; set; }
public ICommand GenerateReportCommand
{
get
{
if (_generateReportCommand == null)
{
_generateReportCommand = new RelayCommand(
p => GenerateReport()
);
}
return _generateReportCommand;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainWindowViewModel()
{
Reports = new List<Report>
{
new Report{ Name = "Students Report", ViewModel = new StudentsReportViewModel()},
new Report{ Name = "Marks Report", ViewModel = new MarksReportViewModel()}
};
SelectedReport = Reports[0];
}
public void GenerateReport()
{
SelectedReport.ViewModel.GenerateReport();
}
}
StudentsReport.xaml
<TextBox Height="25" Width="100" Margin="5" Text="{Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Height="25" Width="100" Margin="5" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
StudentsReportViewModel:
public class StudentsReportViewModel : INotifyPropertyChanged, IReportViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string id;
public string Id
{
get
{
return this.id;
}
set
{
if (value != this.id)
{
this.id = value;
NotifyPropertyChanged();
}
}
}
private string name;
public string Name
{
get
{
return this.name;
}
set
{
if (value != this.name)
{
this.name = value;
NotifyPropertyChanged();
}
}
}
public StudentsReportViewModel()
{
}
public void GenerateReport()
{
System.Windows.MessageBox.Show($"Id = {Id}, Name = {Name}");
}
}
Interface:
public interface IReportViewModel
{
void GenerateReport();
}
Model:
public class Report
{
public string Name { get; set; }
public IReportViewModel ViewModel { get; set; }
}
Your StudentsReport.xaml UserControl is binding to an instance of the StudentsReportViewModel created in the XAML:
<UserControl.DataContext>
<vm:StudentsReportViewModel/>
</UserControl.DataContext>
The Generate Report button however is calling into another instance of the StudentsReportViewModel that is create in the MainWindowVieModel constructor and is stored in the Report class.
Reports = new List<Report>
{
new Report{ Name = "Students Report", ViewModel = new StudentsReportViewModel()},
new Report{ Name = "Marks Report", ViewModel = new MarksReportViewModel()}
};
You need to remove one of these instances so that the UserControl's DataContext is bound to the same view model instance that you are generating the report message from.
I suggest deleting this code from StudentsReport.xaml:
<UserControl.DataContext>
<vm:StudentsReportViewModel/>
</UserControl.DataContext>

WPF MVVM ComboBox data binding

I am trying to create a simple WPF application and bind data to combobox but I am not having any luck. My PeriodList is getting populated fine but is not getting bind to the combobox. Do I need to set the DataContext in XAML or in code behind? Please help, I am very confused.
Here is my XAML
<UserControl x:Class="FinancialControlApp.KeyClientReportView"
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:FinancialControlApp"
mc:Ignorable="d"
d:DesignHeight="300" Width="630">
<UserControl.Resources>
<!-- DataTemplate (View) -->
<DataTemplate DataType="{x:Type local:KeyClientReportModel}">
</DataTemplate>
</UserControl.Resources>
<DockPanel Margin="20">
<DockPanel DockPanel.Dock="Top" VerticalAlignment="Center">
<TextBlock Margin="10,2" DockPanel.Dock="Left" Text="Start Period" VerticalAlignment="Center" />
<ComboBox Name="cmbStartPeriod" Margin="10,2" Width="112" VerticalAlignment="Center" ItemsSource="{Binding PeriodList}">
</ComboBox>
<TextBlock Margin="10,2" DockPanel.Dock="Left" Text="End Period" VerticalAlignment="Center" />
<ComboBox Name="cmbEndPeriod" Margin="10,2" Width="112" VerticalAlignment="Center" ItemsSource="{Binding PeriodList}" />
<!--<Button Content="Save Product" DockPanel.Dock="Right" Margin="10,2" VerticalAlignment="Center"
Command="{Binding Path=SaveProductCommand}" Width="100" />-->
<Button Content="Run" DockPanel.Dock="Left" Margin="10,2"
Command="{Binding Path=GetProductCommand}" IsDefault="True" Width="100" />
</DockPanel>
<!--<ContentControl Margin="10" Content="{Binding Path=PeriodName}" />-->
<ContentControl Margin="10"></ContentControl>
</DockPanel>
</UserControl>
Here is my model
namespace FinancialControlApp
{
public class KeyClientReportModel : ObservableObject
{
private string _periodName;
public string PeriodName
{
get { return _periodName; }
set
{
if (value != _periodName)
{
_periodName = value;
OnPropertyChanged("PeriodName");
}
}
}
List<KeyClientReportModel> _periodList = new List<KeyClientReportModel>();
public List<KeyClientReportModel> PeriodList
{
get { return _periodList; }
set
{
_periodList = value;
OnPropertyChanged("PeriodList");
}
}
}
}
And here is my ViewModel
namespace FinancialControlApp
{
public class KeyClientReportViewModel : ObservableObject, IPageViewModel
{
private KeyClientReportModel _currentPeriod;
private ICommand _getReportCommand;
private ICommand _saveReportCommand;
public KeyClientReportViewModel()
{
GetPeriod();
}
public string Name
{
get { return "Key Client Report"; }
}
public ObservableCollection<KeyClientReportModel> _periodName;
public ObservableCollection<KeyClientReportModel> PeriodName
{
get { return _periodName; }
set
{
if (value != _periodName)
{
_periodName = value;
OnPropertyChanged("PeriodName");
}
}
}
private void GetPeriod()
{
DataSet ds = new DataSet();
DataTable dt = new DataTable();
Helper_Classes.SQLHelper helper = new Helper_Classes.SQLHelper();
ds = helper.getPeriod();
dt = ds.Tables[0];
PeriodName = new ObservableCollection<KeyClientReportModel>();
foreach (DataRow dr in dt.Rows)
{
var period = dr["Period"].ToString();
if (period != null)
{
PeriodName.Add(new KeyClientReportModel { PeriodName = period });
}
//p.PeriodName = dr["Period"].ToString();
}
}
}
}
UPDATE: So I attach a Value Converter to Break into the Debugger and here is what I see. I see
I see 5 items in the list
Change
ItemsSource="{Binding KeyClientReportModel.PeriodList}"
To:
ItemsSource="{Binding PeriodList}"
Make sure your ViewModel is set to the DataContext property of your view.
Set the combobox DisplayMemberPath to the property Name of your KeyClientReportViewModel class.
Or alternatively override the .ToString() method inside the KeyClientReportViewModel class in order to provide the Combobox item display text.
This can help you
------View
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<!-- To get the ViewModel -->
xmlns:viewmodels="clr-namespace:WpfApp1.ViewModels"
Title="MainWindow">
<Window.DataContext>
<!-- Assigning the ViewModel to the View -->
<viewmodels:MainWindowViewModel />
</Window.DataContext>
<DockPanel VerticalAlignment="Center"
DockPanel.Dock="Top">
<TextBlock Margin="10,2"
VerticalAlignment="Center"
DockPanel.Dock="Left"
Text="Start Period" />
<ComboBox Name="cmbStartPeriod"
Width="112"
Margin="10,2"
VerticalAlignment="Center"
ItemsSource="{Binding PeriodName}" // Items in the ViewModel
DisplayMemberPath="Name"/> // Property to display
</DockPanel>
</Window>
------- ViewModel
public class MainWindowViewModel
{
public MainWindowViewModel()
{
var items = new List<KeyClientReportModel>
{
new KeyClientReportModel
{
Name = "First",
Value = 1
},
new KeyClientReportModel
{
Name = "Second",
Value = 1
}
};
PeriodName = new ObservableCollection<KeyClientReportModel>(items);
}
// You don't need to notify changes here because ObservableCollection
// send a notification when a change happens.
public ObservableCollection<KeyClientReportModel> PeriodName { get; set; }
}
public class KeyClientReportModel
{
public int Value { get; set; }
public string Name { get; set; }
}

WPF data templates

I'm getting started with WPF and trying to get my head around connecting data to the UI. I've managed to connect to a class without any issues, but what I really want to do is connect to a property of the main window.
Here's the XAML:
<Window x:Class="test3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=Platforms}"
x:Key="platforms"/>
<DataTemplate DataType="{x:Type custom:Platform}">
<StackPanel>
<CheckBox IsChecked="{Binding Path=Selected}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource platforms}}"/>
</Grid>
Here's the code for the main window:
public partial class MainWindow : Window
{
ObservableCollection<Platform> m_platforms;
public MainWindow()
{
m_platforms = new ObservableCollection<Platform>();
m_platforms.Add(new Platform("PC"));
InitializeComponent();
}
public ObservableCollection<Platform> Platforms
{
get { return m_platforms; }
set { m_platforms = value; }
}
}
Here's the Platform class:
public class Platform
{
private string m_name;
private bool m_selected;
public Platform(string name)
{
m_name = name;
m_selected = false;
}
public string Name
{
get { return m_name; }
set { m_name = value; }
}
public bool Selected
{
get { return m_selected; }
set { m_selected = value; }
}
}
This all compiles and runs fine but the list box displays with nothing in it. If I put a breakpoint on the get method of Platforms, it doesn't get called. I don't understand as Platforms is what the XAML should be connecting to!
Your code looks ok apart from the fact that the Binding on Source on CollectionViewSource is not correct. You probably meant this:
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=MainWindow.Platforms}"
x:Key="platforms"/>
Without this change the Binding actually looked for property Platforms on Application instance.
I would suggest you add the platforms not to the MainWindow but rather set it as the MainWindow's DataContext (wrapped inside a ViewModel).
That way you can very easily bind against it (the binding code would look like ItemsSource={Binding Path=Platforms}).
This is part of WPFs design, that every form should have a explicit DataContext it binds to.
A somewhat more appropriate solution is to give your window a name. A nice convention is _this.
<Window x:Name="_this" x:Class="test3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
Source="{Binding ElementName=_this, Path=Platforms}"
x:Key="platforms"/>
<DataTemplate DataType="{x:Type custom:Platform}">
<StackPanel>
<CheckBox IsChecked="{Binding Path=Selected}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource platforms}}"/>
</Grid>
Here's my updated code, the XAML:
<Window x:Name="_this"
x:Class="test3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test3"
Title="MainWindow" Height="190" Width="177">
<Window.Resources>
<CollectionViewSource
Source="{Binding ElementName=_this, Path=Platforms}"
x:Key="platforms"/>
<DataTemplate x:Key="platformTemplate" DataType="{x:Type custom:Platform}">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="1" IsChecked="{Binding Path=Selected}"/>
<TextBlock Margin="1" Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="23" />
<RowDefinition Height="23" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
ItemsSource="{Binding Source={StaticResource platforms}}"
ItemTemplate="{StaticResource platformTemplate}"/>
<Button Click="OnBuild" Grid.Row="1">Build...</Button>
<Button Click="OnTogglePC" Grid.Row="2">Toggle PC</Button>
</Grid>
</Window>
The code behind the XAML:
public partial class MainWindow : Window
{
ObservableCollection<Platform> m_platforms;
public MainWindow()
{
m_platforms = new ObservableCollection<Platform>();
m_platforms.Add(new Platform("PC"));
m_platforms.Add(new Platform("PS3"));
m_platforms.Add(new Platform("Xbox 360"));
InitializeComponent();
}
public ObservableCollection<Platform> Platforms
{
get { return m_platforms; }
set { m_platforms = value; }
}
private void OnBuild(object sender, RoutedEventArgs e)
{
string text = "";
foreach (Platform platform in m_platforms)
{
if (platform.Selected)
{
text += platform.Name + " ";
}
}
if (text == "")
{
text = "none";
}
MessageBox.Show(text, "WPF TEST");
}
private void OnTogglePC(object sender, RoutedEventArgs e)
{
m_platforms[0].Selected = !m_platforms[0].Selected;
}
}
...and finally the Platform code, enhanced to finish off the two way interaction:
public class Platform : INotifyPropertyChanged
{
private string m_name;
private bool m_selected;
public Platform(string name)
{
m_name = name;
m_selected = false;
}
public string Name
{
get { return m_name; }
set
{
m_name = value;
OnPropertyChanged("Name");
}
}
public bool Selected
{
get { return m_selected; }
set
{
m_selected = value;
OnPropertyChanged("Selected");
}
}
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Using DataContext, it gets even easier!
<Window x:Class="test5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test5"
Title="MainWindow" Height="190" Width="177">
<Window.Resources>
<CollectionViewSource
Source="{Binding Path=.}"
x:Key="platforms"/>
<DataTemplate x:Key="platformTemplate" DataType="{x:Type custom:Platform}">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="1" IsChecked="{Binding Path=Selected}"/>
<TextBlock Margin="1" Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="23" />
<RowDefinition Height="23" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
ItemsSource="{Binding Source={StaticResource platforms}}"
ItemTemplate="{StaticResource platformTemplate}"/>
<Button Click="OnBuild" Grid.Row="1">Build...</Button>
<Button Click="OnTogglePC" Grid.Row="2">Toggle PC</Button>
</Grid>
</Window>
Here's the code behind this:
private ObservableCollection<Platform> m_platforms;
public MainWindow()
{
InitializeComponent();
m_platforms = new ObservableCollection<Platform>();
m_platforms.Add(new Platform("PC"));
m_platforms.Add(new Platform("PS3"));
m_platforms.Add(new Platform("Xbox 360"));
DataContext = m_platforms;
}
public void OnBuild(object sender, RoutedEventArgs e)
{
string text = "";
foreach (Platform platform in m_platforms)
{
if (platform.Selected)
{
text += platform.Name + " ";
}
}
if (text == "")
{
text = "none";
}
MessageBox.Show(text, "WPF TEST");
}
public void OnTogglePC(object sender, RoutedEventArgs e)
{
m_platforms[0].Selected = !m_platforms[0].Selected;
}
Note that I've dropped the need to declare Platforms as a property of the main window, instead I assign it to the DataContext, and the XAML source becomes, simply, "."

Categories