DataTemplate View Renders but collection data does not - c#

I am brand new to XAML, WPF and data binding so this might be a dumb question. I have a VM, AllTransactionViewModel with a collection of PurchaseTransactions. I have a view, via a Resource Dictionary, that renders each PurchaseTransaction in the collection in a ListView. I can get the View to render in the main window but I the data does not populate.
View - AllTransactionsView.Xaml:
<UserControl x:Class="BudgetApp.Views.AllTransactionsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignWidth="300" Height="97">
<UserControl.Resources>
<CollectionViewSource
x:Key="AllTrans"
Source="{Binding Path=AllTransactions}"/>
</UserControl.Resources>
<ListView
DataContext="{StaticResource AllTrans}"
ItemsSource="{Binding}" >
<ListView.View>
<GridView>
<GridViewColumn
Header="Name"
DisplayMemberBinding="{Binding Path=Name}"
Width="Auto"/>
<GridViewColumn
Header="Transaction Type"
DisplayMemberBinding="{Binding Path=TransactionType}" />
<GridViewColumn
Header="Amount"
DisplayMemberBinding="{Binding Path=Amount}"
/>
</GridView>
</ListView.View>
</ListView>
DataTemplate in MainWindowResoures.Xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:BudgetApp.ViewModels"
xmlns:vw="clr-namespace:BudgetApp.Views">
<DataTemplate x:Key="allTransTemplate" DataType="{x:Type vm:AllTransactionViewModel}">
<vw:AllTransactionsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:TransactionViewModel}">
<vw:TransactionView />
</DataTemplate>
MainWindow.XAML:
<Window x:Class="BudgetApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:BudgetApp.ViewModels"
xmlns:vw="clr-namespace:BudgetApp.Views"
Title="MainWindow" Height="590.717" Width="767.194">
<Window.Resources>
<ResourceDictionary Source="MainWindowResources.xaml"/>
</Window.Resources>
<Grid>
<ContentControl
Content="{Binding AllTransactions}"
ContentTemplate="{StaticResource allTransTemplate}">
</ContentControl>
</Grid>
Finally, the AllTransactionViewModel class:
class AllTransactionViewModel: ViewModelBase
{
private BudgetEntities _context;
public AllTransactionViewModel()
{
_context = new BudgetEntities();
AllTransactions = new ObservableCollection<TransactionViewModel>();
GetAllTransactions();
}
public void GetAllTransactions()
{
foreach (var transaction in _context.PurchaseTransactions)
{
AllTransactions.Add(new TransactionViewModel
(
transaction.TransactionDate,
transaction.TransactionType.Description,
transaction.Name,
transaction.Memo,
(double)transaction.Amount,
transaction.IsApproved,
transaction.PurchaseType.Description
)
);
}
}
public ObservableCollection<TransactionViewModel> AllTransactions { get; private set; }
}
What am I doing wrong here?

First, when working with CollectionViewSource the binding (on ListView) should be:
ItemsSource="{Binding Source={StaticResource AllTrans}}"
You don't need the data context in this case. If you have more code than shown, and you actually need data context then leave items source as original and instead change data context:
DataContext="{Binding Source={StaticResource AllTrans}}"
The reason for the "weird" binding use is that you actually need CollectionViewSource.View as the items source. Binding has a special treatment when the source object is CVS and it gets the view automatically. StaticResource doesn't have the special treatment, and you can't use {StaticResource AllTrans.View} since it means to get the key in property View of AllTrans type, a type that doesn't exist.
[As a side note, maybe DataContext="{StaticResource AllTrans}" ItemsSource="{Binding View}" will work, never tried it]
The above should fill the items, but I'm not sure what will happen with the conflict of a data template with data type TransactionViewModel and the fact that you use DisplayMemberBinding on your columns. This might mess the items, but you still should be able to see that there are some items in the list view.

Related

C# WPF: Communication between UserControls und Windows

Hi my Problem is I haven't any Idea how I send Data from my UserControls to other UserControls or Windows in my Project.
My Project is a .Net 7 WPF-Project.
I have one FilterControl where Checkboxes and/or Textboxes filtering a List. The List exists in this FilterControl.
And after this filtering I want to send die filtered List to an other UserControl to show the List.
Aside from that I want to send the List to an Window.
How can I do that?
This is the MainWindow
<Window x:Class="MyProject.MainWindow"
xmlns:FilterCtrl="clr-namespace:MyProject.FilterControl"
xmlns:ListCtrl="clr-namespace:MyProject.ListControl">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<FilterCtrl:FilterControl/>
<ListCtrl:ListControl/>
</Grid>
</Window>
The UserControls:
SubFilter
<UserControl x:Class="MyProject.SubFilter">
<UserControl.DataContext>
<local:SubFilterViewModel/>
</UserControl.DataContext>
<Grid>
<CheckBox Command="{Binding CheckBoxCommand1}" Content="FilterOption1" Checked="{Binding CheckBool1}">
<CheckBox Command="{Binding CheckBoxCommand2}" Content="FilterOption2" Checked="{Binding CheckBool2}">
</Grid>
</UserControl>
FilterControl
<UserControl x:Class="MyProject.FilterControl
xmlns:SubFilterCtrl="clr-namespace:MyProject.SubFilterControl">
<UserControl.DataContext>
<local:FilterViewModel/>
</UserControl.DataContext>
<Grid>
<SubFilterCtrl:SubFilterControl/>
<TextBox Command="{Binding TextBoxCommand}" Text="AnotherFilterOption">
</Grid>
</UserControl>
ListControl
<UserControl x:Class="MyProject.ListControl
xmlns:ListCtrl="clr-namespace:MyProject.ListControl">
<UserControl.DataContext>
<local:FilterViewModel/>
</UserControl.DataContext>
<Grid>
<ListView MaxHeight="500" ItemsSource="{Binding FilteredList}" SelectedItem="{Binding SelectedColumm}">
<ListView.View>
<GridView AllowsColumnReorder="true">
<GridViewColumn DisplayMemberBinding="{Binding Path=Name}" Header="Name" Width="200"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
I Use an Relay Command from this Side:
RelayCommand
The List for Filtering are simple Objects with some Properties. Here only Name.
With the SubFilter I want only filtering with Checkboxes, and want back a List with the booleans from the CheckBoxes
The FilterControl filtering with these List from SubFilter and his own FilterOptions.
The ListControl should only show the List.
Now I have this in my MainWindow.xaml:
<Window x:Class="MyProject.MainWindow"
xmlns:FilterCtrl="clr-namespace:MyProject.FilterControl"
xmlns:ListCtrl="clr-namespace:MyProject.ListControl">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<FilterCtrl:FilterControl DataContext:"{Binding Main}"/>
<ListCtrl:ListControl DataContext:"{Binding Main}"/>
</Grid>
</Window>
In the MainViewModel I have the Variable:
public MainViewModel Main {get { return this;}}
because I want to change the Datacontext if I have an other Window who uses there, but with other Design-Options

WPF TabControl - Add New Tab With Content Template

I am trying to make a demo application to help me understand WPF/MVVM. I have been struggling for 3 days looking at various tutorials and threads. I want to make a tab control with a new tab button (like here) that lets the user create a new tab with specified content template. I create my user control that I want to be the template here:
<UserControl x:Class="MvvmTest.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MvvmTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ListView d:ItemsSource="{d:SampleData ItemCount=5}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
It is just a control with a ListView. So, I want this ListView to be in any new tab that is opened.
Here is my main window with the actual tab control:
<Window x:Class="MvvmTest.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:MvvmTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button Content="New Tab" Margin="703,6,10,401" Click="Button_Click"/>
<TabControl Name= "TabControl1" Margin="0,33,0,-33" Grid.ColumnSpan="2">
</TabControl>
</Grid>
</Window>
In this code-behind, I try to create a new tab programmatically and set the content template to the new control.
private void Button_Click(object sender, RoutedEventArgs e)
{
TabControl1.Items.Add(new TabItem() { ContentTemplate = UserControl1 });
}
This fails. I also tried setting properties in the XAML which also failed. I'm not sure what else to try.
If you're trying to use MVVM, where is your view model? The approach you have so far is not very MVVM because you're using code-behind to add tab items. The MVVM approach would be to bind the ItemSource property of the TabControl to a collection of items and let the view model add the items for you. You also cannot use a UserControl as a ContentTemplate like that without wrapping it in a DataTemplate definition.
The first thing to do is to define some view models:
// MvvmLight (from NuGet) is included for it's INotifyPropertyChanged
// (ViewModelBase) and ICommand (RelayCommand) classes. INotifyPropertyChanged
// is how Binding works between the View and the View Model. You could
// implement these interfaces yourself if you wanted to.
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace MvvmTest
{
public class MainWindowViewModel : ViewModelBase
{
// store our list of tabs in an ObservableCollection
// so that the UI is notified when tabs are added/removed
public ObservableCollection<TabItemViewModel> Tabs { get; }
= new ObservableCollection<TabItemViewModel>();
// this code gets executed when the button is clicked
public ICommand NewTabCommand
=> new RelayCommand(() => Tabs.Add(new TabItemViewModel()
{ Header = $"Tab {Tabs.Count + 1}"}));
}
public class TabItemViewModel : ViewModelBase
{
// this is the title of the tab, note that the Set() method
// invokes PropertyChanged so the view knows if the
// header changes
public string Header
{
get => _header;
set => Set(ref _header, value);
}
private string _header;
// these are the items that will be shown in the list view
public ObservableCollection<string> Items { get; }
= new ObservableCollection<string>() { "One", "Two", "Three" };
}
}
Then you can fix your XAML so that it refers to the view-models that you defined. This requires defining the DataContext for your MainWindow and binding the elements of MainWindow to properties on the view model:
<Window x:Class="MvvmTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MvvmTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<!--Set the DataContent to be an instance of our view-model class -->
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--The Command of the button is bound to the View Model -->
<Button Grid.Row="0" HorizontalAlignment="Right" Width="100"
Content="New Tab"
Command="{Binding NewTabCommand}" />
<!--ItemsSource is bound to the 'Tabs' property on the view-
model, while DisplayMemeberPath tells TabControl
which property on each tab has the tab's name -->
<TabControl Grid.Row="1"
ItemsSource="{Binding Tabs}"
DisplayMemberPath="Header">
<!--Defining the ContentTemplate in XAML when is best.
This template defines how each 'thing' in the Tabs
collection will be presented. -->
<TabControl.ContentTemplate>
<DataTemplate>
<!--The UserControl/Grid were pointless, so I
removed them. ItemsSource of the ListView is
bound to an Items property on each object in
the Tabs collection-->
<ListView ItemsSource="{Binding Items}">
<ListView.View>
<GridView>
<GridViewColumn Header="Some column"/>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
The result is that when you press the button, a new tab gets created and shown

UWP - x:Bind and Binding - FlipView inside AdaptativeGridView - Use only x:Bind

new to .NET ecosystem.
The following code is working fine following MVVM in a blank UWP app.
<Page
x:Class="UWP.Blank.Mvvm.App01.Views.PrincipalPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UWP.Blank.Mvvm.App01"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:md="using:UWP.Blank.Mvvm.App01.Models"
xmlns:vm="using:UWP.Blank.Mvvm.App01.ViewModels"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
x:Name="ThisPrincipalPage">
<Page.Resources>
<DataTemplate x:Name="FlipView_ItemTemplate" x:DataType="md:MyFlipViewItem">
<Grid>
<Image Source="{x:Bind ImageLocation}" Stretch="Uniform" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="AdaptativeGridView_ItemTemplate">
<Grid
Background="White"
BorderBrush="Black"
BorderThickness="1">
<FlipView Width="300" Height="300"
ItemTemplate="{StaticResource FlipView_ItemTemplate}"
ItemsSource="{Binding ElementName=ThisPrincipalPage, Path=ViewModel.FlipViewData}"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid>
<controls:AdaptiveGridView x:Name="AdapGridView1"
ItemsSource="{x:Bind Path=ViewModel.AdaptativeGridViewData}"
ItemTemplate="{StaticResource AdaptativeGridView_ItemTemplate}"/>
</Grid>
</Page>
Which looks like:
enter image description here
The code behind is:
public sealed partial class PrincipalPage : Page
{
// Reference to our view model.
public PrincipalViewModel ViewModel { get; set; }
public PrincipalPage()
{
this.InitializeComponent();
this.ViewModel = new PrincipalViewModel();
}
}
And the ViewModel is an instance of this class:
public class PrincipalViewModel
{
public ObservableCollection<int> AdaptativeGridViewData { get; set; } = new ObservableCollection<int>() { 1, 2, 3, 4 };
public ObservableCollection<MyFlipViewItem> FlipViewData { get; set; } = new ObservableCollection<MyFlipViewItem>();
...
}
that provides two lists, one for the AdaptativeGridView control, and another for all the FlipViews inside of it.
My question is, is there any way I can use x:Bind only? Basically, because it claims to be faster. In case it is not possible, x:Bind is not a substitute of Binding, and makes everything even more confusing.
I tried two basic "intuitive options" which don't work.
Option 1, where ItemsSource property of the FlipView is changed to use x:Bind. Error claims:
Invalid binding path 'ViewModel.FlipViewData' : Property 'ViewModel' not found on type 'DataTemplate'.
Seems FlipView's DataTemplate has a DataContext different than expected. Maybe the DataContext is pointing to ViewModel.AdaptativeGridViewData, but, who knows? It is XAML. This is one of the reasons I am finding XAML an inconvenient since it behaves like a "black box". You know how to do something or you are in trouble.
In this case I tried to use classic Binding with "FindAncestor" utility to point back to ViewModel. In UWP however, AncestorType is not implemented.
(i.e., alike --> ItemsSource="{Binding FlipViewData, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type AdaptativeGridView}}}" )
<DataTemplate x:Key="AdaptativeGridView_ItemTemplate">
<Grid
Background="White"
BorderBrush="Black"
BorderThickness="1">
<FlipView Width="300" Height="300"
ItemTemplate="{StaticResource FlipView_ItemTemplate}"
ItemsSource="{x:Bind Path=ViewModel.FlipViewData}"/>
</Grid>
</DataTemplate>
I am curious since I am not explicitely setting the x:DataType in the DataTemplate. So, why the DataContext is not the same as the AdaptativeGridView or any "normal" control in the Page?
Option 2, which explicitely sets x:DataType of the DataTemplate (note this is indeed working in the other DataTemplate "FlipView_ItemTemplate"). However, for the "AdaptativeGridView_ItemTemplate", there is a runtime error.
Exception thrown: 'System.InvalidCastException' in UWP.Blank.Mvvm.App01.exe
and no more clues.
<DataTemplate x:Key="AdaptativeGridView_ItemTemplate" x:DataType="vm:PrincipalViewModel">
<Grid
Background="White"
BorderBrush="Black"
BorderThickness="1">
<FlipView Width="300" Height="300"
ItemTemplate="{StaticResource FlipView_ItemTemplate}"
ItemsSource="{x:Bind FlipViewData}"/>
</Grid>
</DataTemplate>
Any experience or suggestions are welcome.

How to access this user control in Main Window

I have one user control in my WPF app
<UserControl x:Class="NewWPFApp.ProgressControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Expander Header="{Binding Path=Headerval}">
<StackPanel Margin="10,4,0,0">
<DataGrid
x:Name="dataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=records}"/>
</StackPanel>
</Expander>
</Grid>
and in my Mainwindow when I am doing this
<Window xmlns:NewWPFApp="clr-namespace:NewWPFApp" x:Class="NewWPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox x:Name="peopleListBox" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="AliceBlue">
<NewWPFApp:ProgressControl Height="100" Width="100"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I cant see the output.
If I remove it from the Data template then it works.But not inside the data template.
What am I missing ???
Thanks
ListBox.ItemTemplate specifies how items added to a list box will be displayed. In your case, you uses your own custom control. However, at the same time you added no items to the list box so there is nothing to display. To populate the list box you can use binding e.g.:
<ListBox ItemsSource="{Binding ItemsToBeDisplayed}" x:Name="peopleListBox" >
...
</ListBox>
Where ItemsToBeDisplayed is a property of a view model (if you use MVVM pattern) that returns a collection of objects. If not you can populate ItemsSource without using binding:
var list = new List<People>();
//Add objects to a list
peopleListBox.ItemsSource = list;
I assumed that you have People class that models people you want to display in your application. People class should have Headerval and records properties because you use them in your your ProgressControl control. If you do as described, a new instance of your ProgressControl control will be created for every object in ItemsSource.

Using a ItemsSource with binding gives designer-warnings on properties of DataGrid but not on ListBox

I have a simple view model like:
public class ViewModel {
public int Number {get;set;}
public ObservableCollection<SubViewModel> SubModels {get;set;}
}
public class SubViewModel{
public string SomeText {get;set;}
}
And I want to bind this in the xaml.
Now having a binding in a ListBox like:
<ListBox ItemsSource="{Binding Path=SubModels}">
and then using properties from a SubViewModel like:
<TextBlock Text="{Binding Path=SomeText}" />
gives me no warnings and I even have the "autocomplete" in the designer available.
When doing the same in a DataGrid, Visual Studio shows warnings that it can't find the property "SomeText", since it's looking in the ViewModel class instead of SubViewModel.
When running the application all is fine but this warning and the missing "autocomplete" is quite annoying.
Is there any way to fix this easily?
Edit 1:
Here's the complete source of the XAML:
<Window x:Class="WpfBindingTest.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"
mc:Ignorable="d"
xmlns:wpfBindingTest="clr-namespace:WpfBindingTest"
d:DataContext="{x:Static wpfBindingTest:SampleDataContext.ViewModel}"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBlock Text="{Binding Path=Number}"></TextBlock>
<ListBox ItemsSource="{Binding Path=SubModels}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=SomeText}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=SubModels}" CanUserAddRows="False" HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name" Width="SizeToCells" MinWidth="50" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Path=SomeText}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Window>

Categories

Resources