WPF MVVM ComboBox data binding - c#

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; }
}

Related

WPF: Combobox lost its content after being switch out en in of view

I have a multi tabbel interface where I use a number of ComboBoxes for defining a mapping between two objects. I am working in a MVVM design pattern. Since I do not know the number of mappings I need to create I used an ItemsControl with a DataTemplate to create the required GUI elements. Things seem to work nicely. The Mapping property is correctly setup.
But when the user switches to the 2nd tab and switches back to Tab 1, the data in the mapping structure is incorrect. The be precise: The Actual field is null. The Comboboxes reflect this by being empty.
The combobox that is not part of the ItemsControl, which uses the independent Mapping1 and Things2 properties, does not show this behavior. I am clearly missing something here, but I cannot figure it out.
My data models:
namespace DisconnectedItemTest.Models
{
public class Thing
{
public int Id { get; init; }
public string? Name { get; init; }
public object? TheRest { get; init; }
}
}
namespace DisconnectedItemTest.Models
{
public class Mapping
{
public string? Original { get; set; }
public Thing? Actual { get; set; }
}
}
namespace DisconnectedItemTest.Models
{
public class Mapping1
{
public string? Original { get; set; }
public Thing? Actual { get; set; }
}
}
My ViewModel
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DisconnectedItemTest.Models;
namespace DisconnectedItemTest.ViewModels
{
// ReSharper disable once ClassNeverInstantiated.Global
public partial class Tab1ViewModel : ObservableObject, ITab
{
public string TabName { get; set; } = "Tab 1";
[ObservableProperty] private bool _isEnabled = true;
[ObservableProperty] private ObservableCollection<Mapping> _mapping;
[ObservableProperty] private ObservableCollection<Thing> _things;
[ObservableProperty] private ObservableCollection<Mapping1> _mapping1;
public Tab1ViewModel()
{
_things = new ObservableCollection<Thing>();
for (var i = 1; i <= 5; i++)
{
var thing = new Thing()
{
Id = i,
Name = $"Thing {i}",
TheRest = null
};
_things.Add(thing);
}
_mapping = new ObservableCollection<Mapping>();
_mapping1 = new ObservableCollection<Mapping1>();
for (var i = 1; i <= 2; i++)
{
var mapping = new Mapping()
{
Original = $"Original {i}",
Actual = null
};
_mapping.Add(mapping);
}
_mapping1 = new ObservableCollection<Mapping1>();
for (var i = 1; i <= 2; i++)
{
var mapping1 = new Mapping1()
{
Original = $"Original {i}",
Actual = null
};
_mapping1.Add(mapping1);
}
}
[RelayCommand]
void TriggerBreak()
{
}
}
}
My XAML for the first Tab
<UserControl x:Class="DisconnectedItemTest.Views.Tab1"
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:viewModels="clr-namespace:DisconnectedItemTest.ViewModels"
xmlns:models="clr-namespace:DisconnectedItemTest.Models"
xmlns:views="clr-namespace:DisconnectedItemTest.Views"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=viewModels:Tab1ViewModel}"
d:DesignHeight="450" d:DesignWidth="800" d:Background="White">
<UserControl.Resources>
<DataTemplate DataType="{x:Type models:Mapping}">
<StackPanel Orientation="Horizontal" Margin="0 0 0 5">
<TextBlock Text="{Binding Original}" MinWidth="125"></TextBlock>
<ComboBox ItemsSource="{Binding DataContext.Things, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type views:Tab1}}}"
SelectedItem="{Binding Actual, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
MinWidth="225"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="Comboboxes in ItemsControl" Margin="10" MinHeight="80">
<ItemsControl ItemsSource="{Binding Mapping}" Margin="10 10 10 5"/>
</GroupBox>
<GroupBox Grid.Row="1" Header="2 seperate ComboBoxes" Margin="10" MinHeight="80">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Margin="0 0 0 5">
<TextBlock Text="{Binding Mapping1[0].Original}" MinWidth="125"></TextBlock>
<ComboBox ItemsSource="{Binding Things}"
SelectedItem="{Binding Mapping1[0].Actual, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
MinWidth="225"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 0 0 5">
<TextBlock Text="{Binding Mapping1[1].Original}" MinWidth="125"></TextBlock>
<ComboBox ItemsSource="{Binding Things}"
SelectedItem="{Binding Mapping1[1].Actual, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
MinWidth="225"/>
</StackPanel>
</StackPanel>
</GroupBox>
<Button Grid.Row="2" Width="75" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="10"
Command="{Binding TriggerBreakCommand}">BREAK</Button>
</Grid>
</UserControl>
I used two independent Mapping objects to prevent DataTemplate from ping up the data for the ComboBoxes not in the ItemsControl.
At first I tried to put the Comboboxes in a DataGrid, but after struggling to get the databinding to work correctly, I ran into the same issue as I do now.

How to Create a Filter in WPF using Datagrid and ComboBox

So here I have a MVVM form. the Form contains a Datagrid which is connected to the Databank. I also have a ComboBox which I want to use as a filter option. The Filter option shoud filter by the "AnlV nr" so when the user selects "01" from the ComboBox the datagrid should refresh and show only that "AnlV nr" that have "01" Below I will share you the code and you can see that i've gotten as far as showing the "AnlV" values in the ComboBox but I now do not know how to do the rest and make the filter work. Below is my Viewmodel and the Xaml code.
If anyone can help me with this I would really apreciate it.
Xaml Code:
<Window x:Class="QBondsFrontend.Views.Input.AnlVTexteView"
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:QBondsFrontend.Views.Input" xmlns:input="clr-namespace:QBondsFrontend.ViewModels.Input" d:DataContext="{d:DesignInstance Type=input:AnlVTexteViewModel}"
mc:Ignorable="d"
Title="AnlVTexteView"
Width="800"
MinHeight="400"
Height="490"
MinWidth="1010"
MaxWidth="1010"
UseLayoutRounding="True">
<Grid Background="#A8A8A8" >
<StackPanel VerticalAlignment="Top" Background="#A8A8A8" Orientation="Horizontal" Height="57">
<Label
Content="AnlV Nr.:" Height="35" FontSize="12"/>
<ComboBox Background="LightGray" Height="20" Width="70" ItemsSource="{Binding lstAnlVTexte}" SelectedItem="{Binding Search}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding AnlVPara}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Height="18" Width="68" Margin="5, 0"
Content="Filter löschen" FontSize="11" Style="{StaticResource STL_ButtonStandard}"
x:Name="filterlöschen"
Command="{Binding icdFilterDelete}"/>
</StackPanel>
<StackPanel Background="LightGray" VerticalAlignment="Top" Height="177" Margin="0,57,0,0">
<DataGrid x:Name="datagridXAML"
Height="177"
ItemsSource="{Binding lstAnlVTexte, Mode=TwoWay}"
Style="{StaticResource STL_DataGridReporting}"
CellStyle="{StaticResource STL_DataGridCellReporting}"
ColumnHeaderStyle="{StaticResource STL_DataGridColumnHeaderReporting}"
AlternatingRowBackground="#A8A8A8"
CanUserResizeColumns="False"
>
<DataGrid.Columns>
<DataGridTextColumn Header="AnlV-Nr"
Binding="{Binding AnlVPara}"
Width="60"/>
<DataGridTextColumn Header="gültig ab"
Binding="{Binding TextGueltigAb}"
Width="68"/>
<DataGridTextColumn Header="Text"
Binding="{Binding ParaText}"
Width="750"/>
<DataGridTextColumn Header="Info"
Binding="{Binding Info}"
Width="*"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<StackPanel Background="#A8A8A8" HorizontalAlignment="Center" Margin="10,268,0,141" Width="1010" >
<Label Content="Bearbeitungsbereich" FontWeight="Bold" FontSize="12" Height="33" />
</StackPanel>
<StackPanel>
<StackPanel Orientation="Horizontal" Background="#A8A8A8" HorizontalAlignment="Center"
Width="1010" Margin="0,294,0,0" Height="31">
<Label Height="25" Width="60" Margin="20, 0, 0, 0" Content="AnlV-Nr.:" />
<ComboBox IsEditable="True" Background="gray" Height="22" Width="69" ItemsSource="{Binding AnlVPara}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding lstAnlVTexte}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Height="15" Margin="10, 0, 0, 0" />
<Label Height="26" Width="122" Content="Editierwarnungen" />
<StackPanel Height="48" Width="100"/>
</StackPanel>
<StackPanel Height="22" Orientation="Horizontal">
<Label Margin="20, 0, 0, 0" Content="gültig ab:" Height="27" />
<TextBox Background="LightGray" Height="20" Width="100" />
</StackPanel>
<StackPanel Height="50" Orientation="Horizontal">
<Label Content="Text:" Height="27" Width="38" Margin="42,0,0,10" />
<TextBox Background="LightGray" Width="500" Height="43" />
</StackPanel>
<StackPanel Orientation="Horizontal" >
<Label Content="Info:" Height="27" Width="38" Margin="42,0,0,0" />
<TextBox Background="LightGray" Width="500" Height="20" />
<Button x:Name="BTN_speichern" Width="80" Height="18" Margin="20,0,0,0" Content="Speichern"
Style="{StaticResource STL_ButtonStandard}" Command="{Binding icdSpeichern}"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
ViewModel:
using Newtonsoft.Json;
using QBondsData.DBModels;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Linq;
namespace QBondsFrontend.ViewModels.Input
{
public class AnlVTexteViewModel : BaseViewModel
{
#region data
private string _AnlVPara;
private DateTime _TextGueltigAb;
private string _ParaText;
private string _Info;
private List<AnlVhistText> _lstAnlVTexte;
private string _search;
#endregion
#region constructor
public AnlVTexteViewModel()
{
icdFilterDelete = new RelayCommand<object>(parameter => filterdelete(), parameter => true);
icdSpeichern = new RelayCommand<object>(parameter => speichern(), parameter => true);
GetData();
}
#endregion
#region members
public ICommand icdFilterDelete { get; set; }
public ICommand icdSpeichern { get; set; }
private string Search
{
get { return _search; }
set
{
_search = value;
OnPropertyChanged("Search");
}
}
public string AnlVPara
{
get
{
return _AnlVPara;
}
set
{
_AnlVPara = value;
OnPropertyChanged("AnlVPara");
}
}
public DateTime TextGueltigAb
{
get
{
return _TextGueltigAb;
}
set
{
_TextGueltigAb = value;
OnPropertyChanged("TextGueltigAb");
}
}
public string ParaText
{
get
{
return _ParaText;
}
set
{
_ParaText = value;
OnPropertyChanged("ParaText");
}
}
public string Info
{
get
{
return _Info;
}
set
{
_Info = value;
OnPropertyChanged("Info");
}
}
public List<AnlVhistText> lstAnlVTexte
{
get { return _lstAnlVTexte; }
set
{
_lstAnlVTexte = value;
OnPropertyChanged("lstAnlVTexte");
}
}
#endregion
#region methods
private void filterdelete()
{
}
private void speichern()
{
}
private async Task GetData()
{
HttpResponseMessage Response = await Globals.SendRequest("AnlVTexte/GetAnlVTexte"
, "GET");
if (Response.IsSuccessStatusCode)
{
lstAnlVTexte = JsonConvert.DeserializeObject<List<AnlVhistText>>
(await Response.Content.ReadAsStringAsync());
}
else
{
lstAnlVTexte = new List<AnlVhistText>();
Application.Current.Dispatcher.Invoke((Action)delegate
{
Globals.CloseReportByHash(this.GetHashCode()
, "Fehler! (HTTP Status " + Response.StatusCode + ")." +
"Kontaktieren Sie den Support.");
});
}
}
#endregion
}
}
When you change the type of lstAnlVTexte to ICollectionView you get two events CurrentChanged and CurrentChanging which you can handle in your viewmodel. From the ICollectionView you can get the CurrentItem.
Like this:
private List<AnlVhistText> _anlVTexte = new List<AnlVhistText>();
public AnlVTexteViewModel()
{
[...]
lstAnlVTexte = new CollectionView(_anlVTexte);
lstAnlVTexte.CurrentChanged += SelectionChanged; // raised after the current item has been changed.
lstAnlVTexte.CurrentChanging += SelectionChanging; // raise before changing the current item. Event handler can cancel this event.
}
private void SelectionChanged(object sender, EventArgs e)
{
var selectedItem = lstAnlVTexte.CurrentItem;
}
private void SelectionChanging(object sender, CurrentChangingEventArgs e)
{
}
private ICollectionView _lstAnlVTexte;
public ICollectionView lstAnlVTexte
{
get { return _lstAnlVTexte; }
set
{
_lstAnlVTexte = value;
OnPropertyChanged("lstAnlVTexte");
}
}
Here's a sample using the community toolkit mvvm and linq.
If you're not familiar, the toolkit does code generation.
This is a simple scenario to illustrate the approach.
Mainwindowviewmodel.
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private int selectedFilterInt = -1;
partial void OnSelectedFilterIntChanged(int newValue)
{
FilteredList = new ObservableCollection<MyItem>(FullList.Where(x=>x.Category == selectedFilterInt).ToList());
}
public List<int> FilterOptions { get; set; } = new List<int> {1,2,3};
private List<MyItem> FullList= new List<MyItem>
{
new MyItem{ Category = 1},
new MyItem{ Category = 1},
new MyItem { Category = 1 },
new MyItem { Category = 2 },
new MyItem { Category = 2 },
new MyItem { Category = 3 }
};
[ObservableProperty]
private ObservableCollection<MyItem> filteredList = new ObservableCollection<MyItem>();
public MainWindowViewModel()
{
FilteredList = new ObservableCollection<MyItem>(FullList);
}
}
There's a full list of all the items.
But a filtered list is going to be bound to the itemssource of my listbox ( equivalent to your datagrid ).
Due to the code generated, when selectedFilterInt changes, OnSelectedFilterIntChanged will be called. It's got a handler listening for property changed of SelectedFilterInt if you dug into the generated code.
That method uses a linq where to filter the full list into filtered list.
Setting that filtered list property raises property changed and the view re reads the new collection.
MainWindow. ( I did mention this is simplified )
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<ComboBox SelectedItem="{Binding SelectedFilterInt}"
ItemsSource="{Binding FilterOptions}"/>
<ListBox ItemsSource="{Binding FilteredList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Category}"/>
<TextBlock Text="{Binding Comment}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Window>
and MyItem
public partial class MyItem : ObservableObject
{
[ObservableProperty]
private int category = 0;
[ObservableProperty]
private string comment = "Some test string";
}
Which is a bit underwhelming visually but works:
In your code you need to get all the data into a collection.
Call that FulList.
You then need another collection which will be the filtered data.
Call this FilteredList.
Bind itemssource to FilteredList
Initially, you presumably want FilteredList to be = FullList
Then when the user selects something in the combobox you need to react to that.
You could bind selecteditem to a property and act in the setter or handle propertychanged like my code does.
However you do it, you get a new integer.
You then use that to filter FullList into a new collection which will replace the bound FilteredList.
You also need to somehow have one entry per AnlV nr whatever that is in your combobox.
AnlV nr isn't going to work as a property name since it's got a space but it is the equivalent to Category in my sample.
You will use that selected value in the linq.
Substitute the name of that property for Category. Substitute the type of whatever your collection is. Maybe that's AnlVhistText. I'm not sure.

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;
}
}
}

Closing an Open Window Using MVVM Pattern, Produces System.NullReferenceException error

I'm trying to learn MVVM pattern using WPF C#. And I'm running into an error when trying to close an opened window after saving information to an sqlite database. When the command to save a new contact is raised, I am getting an error on HasAddedContact(this, new EventArgs());
Error: System.NullReferenceException: 'Object reference not set to an instance of an object.'
My ViewModel:
public class NewContactViewModel : BaseViewModel
{
private ContactViewModel _contact;
public ContactViewModel Contact
{
get { return _contact; }
set { SetValue(ref _contact, value); }
}
public SaveNewContactCommand SaveNewContactCommand { get; set; }
public event EventHandler HasAddedContact;
public NewContactViewModel()
{
SaveNewContactCommand = new SaveNewContactCommand(this);
_contact = new ContactViewModel();
}
public void SaveNewContact()
{
var newContact = new Contact()
{
Name = Contact.Name,
Email = Contact.Email,
Phone = Contact.Phone
};
DatabaseConnection.Insert(newContact);
HasAddedContact(this, new EventArgs());
}
}
SaveNewContactCommand:
public class SaveNewContactCommand : ICommand
{
public NewContactViewModel VM { get; set; }
public SaveNewContactCommand(NewContactViewModel vm)
{
VM = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
VM.SaveNewContact();
}
}
NewContactWindow.Xaml.Cs code behind:
public partial class NewContactWindow : Window
{
NewContactViewModel _viewModel;
public NewContactWindow()
{
InitializeComponent();
_viewModel = new NewContactViewModel();
DataContext = _viewModel;
_viewModel.HasAddedContact += Vm_ContactAdded;
}
private void Vm_ContactAdded(object sender, EventArgs e)
{
this.Close();
}
}
Adding additional code where I call the new window:
public class ContactsViewModel
{
public ObservableCollection<IContact> Contacts { get; set; } = new ObservableCollection<IContact>();
public NewContactCommand NewContactCommand { get; set; }
public ContactsViewModel()
{
NewContactCommand = new NewContactCommand(this);
GetContacts();
}
public void GetContacts()
{
using(var conn = new SQLite.SQLiteConnection(DatabaseConnection.dbFile))
{
conn.CreateTable<Contact>();
var contacts = conn.Table<Contact>().ToList();
Contacts.Clear();
foreach (var contact in contacts)
{
Contacts.Add(contact);
}
}
}
public void CreateNewContact()
{
var newContactWindow = new NewContactWindow();
newContactWindow.ShowDialog();
GetContacts();
}
}
ContactsWindow.Xaml
<Window x:Class="Contacts_App.View.ContactsWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="Contacts Window" Height="320" Width="400">
<Window.Resources>
<vm:ContactsViewModel x:Key="vm"/>
</Window.Resources>
<StackPanel Margin="10">
<Button
Content="New Contact"
Command="{Binding NewContactCommand}"/>
<TextBox Margin="0,5,0,5"/>
<ListView
Height="200"
Margin="0,5,0,0"
ItemsSource="{Binding Contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
NewContactWindow.Xaml
<Window x:Class="Contacts_App.View.NewContactWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Window.Resources>
<vm:NewContactViewModel x:Key="vm"/>
</Window.Resources>
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Phone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding Source={StaticResource vm}, Path=SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>
You're creating NewContactWindow's viewmodel in the constructor, correctly assigning it to DataContext, and correctly adding a handler to that event. Unfortunately, you also create a second instance of the same viewmodel in resources, and you manually set the Source property of all the bindings to use the one in the resources, which doesn't have the event handler.
Window.DataContext, which you set in the constructor, is the default Source for any binding in the Window XAML. Just let it do its thing. I also removed all the redundant Mode=TwoWay things from the Bindings to TextBox.Text, since that property is defined so that all bindings on it will be TwoWay by default. I don't think UpdateSourceTrigger=PropertyChanged is doing anything necessary or helpful either: That causes the Binding to update your viewmodel property every time a key is pressed, instead of just when the TextBox loses focus. But I don't think you're doing anything with the properties where that would matter; there's no validation or anything. But TextBox.Text is one of the very few places where that's actually used, so I left it in.
You should remove the analagous viewmodel resource in your other window. It's not doing any harm, but it's useless at best. At worst, it's an attractive nuisance. Kill it with fire and bury the ashes under a lonely crossroads at midnight.
<Window x:Class="Contacts_App.View.NewContactWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Contact.Name, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Contact.Email, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Contact.Phone, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>

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

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;
}
}
}

Categories