DataTemplate for ViewModel slow to display - wpf

I have a simple MainWindowView
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type local:ClassListForTeacherViewModel}">
<local:ClassListForTeacher />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="109*"/>
<ColumnDefinition Width="408*"/>
</Grid.ColumnDefinitions>
<local:NavigationControl Grid.Column="0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Width="Auto"/>
<ContentControl Content="{Binding MainView}"
Grid.Column="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Width="Auto"/>
</Grid>
<Window>
and a MainWindowViewModel
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel(string name)
: this(name, null)
{
}
public MainWindowViewModel(string name, ViewModelBase mainView)
{
Name = name;
MainView = mainView;
NavigateToTeacherClassesCommand = new DelegateCommand(o => NavigateToTeacherClasses());
}
//
// Properties
//
public string Name { get; private set; }
private ViewModelBase _MainView;
public ViewModelBase MainView
{
get
{
return _MainView;
}
private set
{
_MainView = value;
RaisePropertyChangedEvent("MainView");
}
}
public ICommand NavigateToTeacherClassesCommand { get; private set; }
//
// Functions
//
private void NavigateToTeacherClasses()
{
MainView = new ClassListForTeacherViewModel();
}
}
The ClassListForTeacher view only contains a ListView control with no logic (yet). When I click the Label in the NavigationControl in the MainWindowView, the ClassListForTeacher is set on the MainView property of MainWindowView and is displayed via the DataTemplate. This is barebones program; no loading data yet. My problem is the display takes too long, maybe about a full 3 seconds. It is my first time trying out MVVM, I see this is a common technique (using DataTemplate), is this normal?
I also noticed that the ViewModel for ClassListForTeacher is called twice but I have no other code calling it. One from the NavigateToTeacherClasses function, the other I don't know. When WPF actually resolves the DataTemplate (which is a UserControl), is the constructor called again? If so, How do I prevent this?
EDIT:
XAML for NavigationControl
<UserControl...
<StackPanel>
<Expander Header="Grading" IsExpanded="True">
<StackPanel Background="#FFE5E5E5">
<Label Content="My Classes">
<Label.InputBindings>
<MouseBinding Command="{Binding NavigateToTeacherClassesCommand}" MouseAction="LeftClick" />
</Label.InputBindings>
</Label>
</StackPanel>
</Expander>
</StackPanel>
</UserControl>
XAML for ClassListForTeacher:
<UserControl...
<Grid x:Name="LayoutRoot">
<ListView>
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>

Related

View is not binding correctly to ViewModel

I cannot get the View to bind correctly to the ViewModel. When it displays, it only shows the string version of the ViewModel.
I have seen: Setting Window.Content to ViewModel - simple data template not working. But the link is no longer available.
I'm trying to use https://msdn.microsoft.com/en-us/magazine/dd419663.aspx, as a template.
MainWindow.xaml
<Window x:Class="DemoApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:DemoApp.ViewModel"
xmlns:vw="clr-namespace:DemoApp.View">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:TestViewModel}">
<vw:TestView/>
</DataTemplate>
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel Width="120">
<Button
Command="{Binding Path=CloseCommand}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
VerticalContentAlignment="Bottom"
Width="16" Height="16"/>
<ContentPresenter
Content="{Binding Path=DisplayName}"
VerticalAlignment="Center"/>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4" />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border
Grid.Column="2"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Header="Workspaces"
Style="{StaticResource MainHCCStyle}" />
</Border>
</DockPanel>
</Window>
MainWindowViewModel.cs
// ommitted for clarity. This is directing to the view model correctly. It's the binding between View and ViewModel that is not
TestView.xaml
public class TestViewModel : WorkspaceViewModel, INotifyPropertyChanged,
{
public Model.Test _test;
public string DisplayName {get; set;}
public class TestViewModel(Model.Test t)
{
DisplayName = "Test Display Name";
_model = t;
}
// INofifyPropertyChanged Members removed for clarity
}
Test.cs
public class Test
{
public string FirstName {get; set;}
public string LastName {get; set;}
public static DisplayTest()
{
return new Test();
}
}
Displays:
DemoApp.ViewModel.TestViewModel;
However, when I go to the MainWindow.xaml and actually type in into a DockPanel, it will display correctly...
Thank you!!
UPDATE:
MainWindowViewModel.cs Properties
public ReadOnlyCollection<CommandViewModel> Commands
{
get
{
if (_commands == null)
{
List<CommandViewModel> cmds = this.CreateCommands();
_commands = new ReadOnlyCollection<CommandViewModel>(cmds);
}
return _commands;
}
}
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
_workspaces.CollectionChanged += this.OnWorkspacesChanged;
}
return _workspaces;
}
}
In the View there was a Data Context Declared. This was confusing the binding it looks like. Once the Data Context in the View was removed and the MainWindowResourses kept the data context, the view is displayed as it should.

add buttons to table arrangement dynamically in WPF

I would like to make a 2-columns table arrangement and to add buttons to the table at runtime.
what I did is defining nested StackPanels similar to this.
<StackPanel MinWidth="500" MaxWidth="800" MaxHeight="400" HorizontalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Foreground="Black" Margin="0,0,0,5" FontSize="20">Some Title</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="" MinWidth="100" MinHeight="100" Margin="10,0,0,10"></Button>
<Button Content="" MinWidth="100" MinHeight="100" Margin="10,0,0,10"></Button>
</StackPanel>
</StackPanel>
Is this a correct starting, or there is a better and easier arrangement?
You could use a model-view implementation with ListView and bind it to a collection of items which have the respective handler for the button:
WPF:
<ListView ItemsSource={Binding MyItems}>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding={Binding Name}></GridViewColumn>
<GridViewColumn Header="Button">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Command={Binding ButtonPress}>Click me</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You can also set the ItemsSource inside the .cs file for your view as well, otherwise use a ViewModel class to handle your view and create a ObservableCollection<MyItemWrapper> property which will hold all the table items.
ViewModel:
public class MyViewModel
{
private ObservableCollection<MyItemWrapper> _myItems;
public MyViewModel()
{
_myItems = new ObservableCollection<MyItemWrapper>();
//// add your initial items
}
public ObservableCollection<MyItemWrapper> MyItems
{
get { return _myItems; }
}
}
View:
public partial class MyView : UserControl
{
public MyView(MyViewModel viewModel)
{
DataContext = viewModel;
InitializeComponents()
}
}
MyItem and MyItemWrapper
public class MyItem
{
public string Name { get; set; }
public object Data { get; set; }
}
public class MyItemWrapper
{
private MyItem _item;
public MyItemWrapper(MyItem item)
{
_item = item;
ButtonPress = new DelegateCommand<object>(OnButtonPress);
}
public string Name
{
get { return _item.Name; }
}
public DelegateCommand<object> ButtonPress { get; private set; }
private void OnButtonPress(object args)
{
System.Diagnostics.Debug.WriteLine("Button pressed for: " + Name);
}
}
This will ultimately be able to add/remove items at runtime by using MyItems inside the view model and have your list view always update automatically.
I got it working by using WrapPanel, and I will share what I have did, Thanks for Vlad for binding part:
<ListBox ItemsSource="{Binding CartItemC.CartItems}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="wrapPanel" MaxWidth="300" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel MaxWidth="300">
<Button Command="{Binding ButtonPress}">Click me</Button>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You mentioned you want it dynamically, well you don't want to use StackPanel as you would end up maintaining the width and height of the controls inside it. Given you want the controls to automatically resize its own size.
The best solution is to use Grid if you really want to make a 2 column table arrangement.
Take a look at this example.
Sample code
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>

WPF ListBox: Binding to an ObservableCollection

I'm binding my ListBox to an ObservableCollection at runtime. Upon clicking a button, one of the items in my collection gets modified, but the corresponding ListBox item doesn't update itself accordingly. I have gone thru several similar SO articles and other help material and it seems like I'm doing everything they've asked for, but no luck. Everything seems to load and bind correctly, but when I change "IsLoading" property of an item in my collection, the Visibility of the Grid (see DataTemplate below) which is bound to IsLoading property doesn't change.
Following is my ListBox XAML:
<ListBox Name="lstItems">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Name="ListBoxGrid">
<Grid.RowDefinitions>
<RowDefinition Height="120"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" IsChecked="{Binding IsSelected}" />
<Image Grid.Column="1" Width="50" Stretch="Uniform" Source="{Binding Image}" />
<TextBlock Grid.Column="2" Text="{Binding Path=ImageFilePath}" />
<Grid Grid.Column="3" Visibility="{Binding IsLoading, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, Mode=TwoWay, BindsDirectlyToSource=True, Converter={StaticResource BooleanToVisibilityConverter1}}">
<my:LoadingAnimation x:Name="SendAnimation" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And here's my BO:
public class Order : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool IsSelected { get; set; }
public string ImageFilePath { get; set; }
public ImageSource Image { get; set; }
private bool mIsSending = false;
public bool IsSending
{
get { return mIsSending; }
set
{
mIsSending = value;
if (PropertyChanged != null)
PropertyChanged(null, new PropertyChangedEventArgs("IsSending"));
}
}
}
And this is how I create the collection and bind it:
ObservableCollection<Order> mOrders = new ObservableCollection<Order>();
public MainWindow()
{
InitializeComponent();
lstItems.ItemsSource = mOrders;
}
Nevermind. Sometimes it is that you spend hours digging the problem, finally get frustrated, post it on SO, and the next 2 minutes you figure it out yourself. For any future reader, the only problem was that I was sending null in PropertyChanged event. As soon as I changed that to this, things started working like a charm.

Silverlight PropertyGrid-like binding

I know that there are 3rd party silverlight property grids (in fact my company owns one) so please dont suggest 3rd party controls : I am trying to learn more about binding in xaml with this question.
I am writing a Silverlight front end as a sign facade to launch SSRS, and php based reports.
I have created a Report class with information about the report, and it has a parameters collection containing the information about parameters that need to be filled in to run the report.
My plan is to create a silverlight property grid that is bound to the Parameters collection of the Report.
Here's a simpler version of the classes:
public class Report
{
public int ReportId { get; set; }
public string ReportName { get; set; }
public string Description { get; set; }
private List<ReportParameter> _Parameters = new List<ReportParameter>();
public List<ReportParameter> Parameters
{
get { return _Parameters; }
set { _Parameters = value; }
}
}
public class ReportParameter
{
public int ReportId { get; set; }
public string ParameterName { get; set; }
public string DataTemplateName { get; set; }
public bool IsRequired { get; set; }
}
I was hoping to use the DataTemplateName property of ReportParameter to bind to data templates: for example if I have a parameter that is a date, I want to be able to set DataTemplateName="MyDatePicker" and then DataTemplate={StaticResource {Binding DataTemplateName}} and have that row use a DataTemplate defined in the Resources for editing the parameter value.
Here's some XAML I am using to try to get it to work:
<UserControl x:Class="ReportLauncherWorkbench.MainPage"
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:ReportLauncherWorkbench"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:Report x:Key="MyData"
ReportName="Rick Report"
Description="Great report, try it!"
ReportId="0"
>
<local:Report.Parameters>
<local:XReportParameter DataTemplateName="DatePickerTemplate"
ParameterName="StartDate"
IsRequired="True"
Tooltip="Please enter the start date"
/>
<local:XReportParameter DataTemplateName="CheckBoxTemplate"
ParameterName="AmIHot"
IsRequired="True"
Tooltip="Please check here if you are hot"
/>
</local:Report.Parameters>
</local:Report>
<DataTemplate x:Key="DatePickerTemplate">
<StackPanel Orientation="Horizontal">
<TextBox />
<Button Content="..."/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="CheckBoxTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource MyData}">
<Grid x:Name="Test1" Background="White">
<StackPanel>
<TextBlock Text="{Binding ReportName}"/>
<TextBlock Text="{Binding Description}"/>
<ListBox ItemsSource="{Binding Parameters}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ParameterName}" Grid.Column="1" Margin="5"/>
<ListBox ItemsSource="{Binding}" Grid.Column="2" Width="100" ItemTemplate="{StaticResource {Binding DataTemplateName}}">
<!-- I want to somehow bind which DataTemplate is rendered-->
</ListBox>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Grid>
</UserControl>
Thanks!
I figured it out - with the help of a great FAQ on the silverlight forum:
http://forums.silverlight.net/p/95440/218611.aspx
Silverlight does not have the DataTemplateSelector class that WPF does, which would have solved the problem.
In section titled
7.1 What data binding features of WPF are not yet supported in Silverlight? Is there a workaround?
There is a simple workaround that for DataTemplateSelector functionality.
So here's how I fixed it in my code sample:
Replace the Listbox with the following:
<ListBox ItemsSource="{Binding Parameters}">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" Loaded="ContentControl_Loaded"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And then fill in the ContentControl_Loaded event in the code behind with:
private void ContentControl_Loaded(object sender, RoutedEventArgs e)
{
ContentControl cc = (ContentControl) sender;
XReportParameter p = (XReportParameter)cc.DataContext;
cc.ContentTemplate = (DataTemplate)this.Resources[p.DataTemplateName];
}
Works great!

databinding a Dataset to a listbox...Datatemplate needs to display from multiple columns

I was trying to figure this out for quite some time.I want a Databind a listbox with a Dataset.Simple as it can get.But my problem is that i want my datatemplate to display Columns from two tables,in the dataset.I have tried many samles..but everything i found just gives the dataset as datacontext and gives a single table as itemsource.But my condition is that i want more than one table in my datatemplate..
For eg:
<DataTemplate x:Key="EmployeeDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Margin="5" BorderBrush="Black" BorderThickness="1">
<Image Source="{Binding Path=Mast/Image}" Stretch="Fill" Width="50" Height="50" />
</Border>
<StackPanel Grid.Column="1" Margin="5">
<StackPanel Orientation="Horizontal" TextBlock.FontWeight="Bold" >
<TextBlock Text="{Binding Path=Mast/Firstname}" />
<TextBlock Text="{Binding Path=Mast/Lastname}" Padding="3,0,0,0"/>
</StackPanel>
<TextBlock Text="{Binding Path=Details/Age}" />
<TextBlock Text="{Binding Path=Details/Role}" />
</StackPanel>
</Grid>
</DataTemplate>
Any way to do this..? I am confused...!
I tried giving the Dataset as datacontext and Itemsource as {Binding} But only one row is displayed...
You should create a view model class that exposes three properties:
MasterTable of type IEnumerable<MasterTableRow>
SelectedMaster of type DataRowView
MasterDetails of type IEnumerable<DetailsTableRow>
In your view model, put your instance of your DataSet, and return the appropriate values for the properties. To wrap it all up, you should implement INotifyPropertyChanged and fire change notifications for SelectedMaster and MasterDetails whenever SelectedMaster changes.
Remember to set the view model as the DataContext for the bindings.
Here's how it might look like:
public partial class ViewModel : INotifyPropertyChanged
{
DataSet1 ds;
DataRowView selectedMaster;
public IEnumerable<DataSet1.MasterTableRow> MasterTable
{
get { return ds.MasterTable; }
}
public DataRowView SelectedMaster
{
get { return selectedMaster; }
set
{
if (selectedMaster != value)
{
selectedMaster = value;
OnPropertyChanged("MasterDetails");
OnPropertyChanged("SelectedMaster");
}
}
}
public IEnumerable<DataSet1.DetailsTableRow> MasterDetails
{
get
{
var masterRow = (DataSet1.MasterTableRow)SelectedMaster.Row;
return masterRow.GetDetailsTableRows();
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
#endregion
}
In XAML, the bindings might look like this:
<ListBox ItemsSource="{Binding MasterTable}"
SelectedItem="{Binding SelectedMaster}"
ItemTemplate="{StaticResource MasterTemplate}"/>
<ListBox ItemsSource="{Binding MasterDetails}"
ItemTemplate="{StaticResource DetailsTemplate}"/>

Resources