Trouble understanding DataBindings in WPF - c#

I'm an absolute beginner in WPF and tried to setup a simple DataBinding that updates the text of a TextBlock based on the text value in a TextBox when you click a button. I got it to work, but i found two different variants of doing it and in both cases something seems off.
Variant 01 works just as it should, but the fact that the target updates the source seems off.
In Variant 02 the source updates the target, but the UpdateSourceTrigger is useless and the only thing the mode does is blocking the target from getting updated, so i can do it manually.
Both variants get the thing done, but in both cases there is something that bothers me and seems off. So what's the 'right' way of doing this and DataBindings in general?
C# Code for both variants:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace TestProject {
public partial class MainWindow {
public MainWindow() {
InitializeComponent();
// Value propagation: Target(InputFieldVariant01) -> Source(OutputFieldVariant01)
Binding binding = new Binding("Text");
binding.Source = OutputFieldVariant01;
binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
binding.Mode = BindingMode.OneWayToSource;
InputFieldVariant01.SetBinding(TextBox.TextProperty, binding);
// Value propagation: Source(InputFieldVariant02) -> Target(OutputFieldVariant02)
Binding binding2 = new Binding("Text");
binding2.Source = InputFieldVariant02;
binding2.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
binding2.Mode = BindingMode.OneWayToSource; // blocks the updating of the OutputField i guess (?)
OutputFieldVariant02.SetBinding(TextBlock.TextProperty, binding2);
}
private void refreshBtnVariant01_refreshTextBlock(object sender, RoutedEventArgs e) {
InputFieldVariant01.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
private void refreshBtnVariant02_refreshTextBlock(object sender, RoutedEventArgs e) {
OutputFieldVariant02.GetBindingExpression(TextBlock.TextProperty)?.UpdateTarget();
}
}
}
and here is my .xaml:
<Window x:Class="TestProject.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:TestProject"
mc:Ignorable="d"
Title="MainWindow" Height="120" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="2" TextAlignment="Center">Variant 01</TextBlock>
<TextBox Grid.Row="2" Grid.Column="2" Name="InputFieldVariant01" MinWidth="100"></TextBox>
<Button Grid.Row="3" Grid.Column="2" Content="Refresh TextBlock" Click="refreshBtnVariant01_refreshTextBlock" Margin="0, 5"></Button>
<TextBlock Grid.Row="4" Grid.Column="2" Name="OutputFieldVariant01"></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="4" TextAlignment="Center">Variant 02</TextBlock>
<TextBox Grid.Row="2" Grid.Column="4" Name="InputFieldVariant02" MinWidth="100"></TextBox>
<Button Grid.Row="3" Grid.Column="4" Content="Refresh TextBlock" Click="refreshBtnVariant02_refreshTextBlock" Margin="0, 5"></Button>
<TextBlock Grid.Row="4" Grid.Column="4" Name="OutputFieldVariant02"></TextBlock>
</Grid>
</Window>

A few points, you are doing all this binding stuff in code. I suggest looking into MVVM patterns since you are new. The basic premise is
M = Model - where the underlying data is coming from / stored to
V = View - the user interface presentation context
VM = ViewModel - the glue getting/sending data to ex: sql database, but also making available to the view / end user.
It can typically be found that you create a view model object such as a class and it has public getter/setter properties on it. When the object is created within your view constructor and set as the DataContext, all bindings can be done directly within the xaml. The class does not need to know what the view does with it, the view doesnt need to know how the data is available. So you can simplify much of this such as
namespace TestProject
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
}
Now, in another class such as example indicates above
namespace TestProject
{
public class MyViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged Members
private string _someString = "test string";
public string SomeString
{
get { return _someString; }
set
{
_someString = value;
RaisePropertyChanged(nameof(SomeString));
}
}
private int _someNumber = 18;
public int SomeNumber
{
get { return _someNumber; }
set
{
_someNumber = value;
RaisePropertyChanged(nameof(SomeNumber));
}
}
public List<SomeTableStructureFromDatabase> ListOfData { get; }
public MyViewModel()
{
ListOfData = SomeMethodToGetDataFromSQLDatabase();
}
}
}
So, in the above sample, you can see the PUBLIC get/set with some default values to the string and numeric values. Yes, I also included the INotifyPropertyChanged extension to the class. You can read more on that later, but allows view components to trigger refresh when things change either internally to the class to refresh the view, or being pushed from the view back to the view model.
Now, how to handle the view. As YOU are developing, you just need to know the names of the pieces on the view model that are exposed publicly. Then, in the xaml, identify the bindings directly. Again, the view should not know how or where the data parts are coming from, just what they are called and exposed as. So the VIEW (MainWindow.xaml) might have your entry textblocks/textbox, etc as:
<TextBox MinWidth="100" [yes, you can assign grid row/columns as normal]
Text="{Binding SomeString, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Width="45" MaxLength="4"
Text="{Binding SomeNumber, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />
Notice the direct binding of the textboxes. Since the MainWindow has its DataContext based on the MyViewModel class created in its constructor, the publicly exposed properties are available to bind to. It can all be handled in the xaml. You just need to know what the property is called on the class. You dont need to explicitly NAME each of the controls so the .CS code has to know the view side name as well.
Also, your context of forcing the mode as ONE WAY TO SOURCE is not going to be as common as you might think. Think of it this way. You want to pull data from a database, expose it for being edited and then saved back. If one-way to the source, you cant get and push TO the field to show the user. So, explicitly stating the mode is one way you might shy away from unless truly needed.
Anyhow, hope this might open your mindset to additional reading and understanding of some bindings. Lastly, as I sampled the class with default text string and numeric values, when you do run the form, you should see those values default when the form is presented. Also, as a numeric (or even date/time as applicable to DatePicker control), you dont have to worry about data conversion from text entry and making sure numeric or bogus text. The binding will only update the value if it is the proper data type for the property being bound to.

The way I was taught binding is by implementing the INotifyPropertyChanged interface (See docs #MSDN). For collections there is an ObservableCollection Type.
You may want to look at MVVM pattern later on but for now just focus on Binding.
In XAML there is a special syntax called XAML Markup Extension.
Here is an example #MSDN.

Related

MVVM how to pass object from main window to user control? [duplicate]

This question already has answers here:
What is DataContext for?
(4 answers)
Closed 4 months ago.
sorry about this question. I know MVVM exist for many years but each time I try to code something with it I face the same issue again and again ans I'm still looking for a real good tutorial about this.
Let's consider we have a main window (MainWindow.xaml) with its view model (MainViewModel.cs).
This window has a grid, in my grid I define 2 user controls. Whatever it is. One is on the left, one on the right. On my main window I have create, in MainViewModel.cs an engine:
internal class MainWindowViewModel
{
public MainWindowViewModel()
{
QCEngine qcEngine = new();
}
}
This engine is my unique model and contains a complex code that read data. Whatever. This engine has a public list of value. I want to display these values on my left and right panels in different ways. Again whatever. The display is not my issue.
My issue is how I pass this list or the entire engine reference to my panels? I'm really lost. I can do this in few seconds with any classic WinForms but I never figure out how to do in MVVM. I'm at this moment where I give up MVVM to do classic WinForms. This time I want to understand.
Can you help me?
My QC engine is a RFID reader. It already works fine as console application. All parameters are in a config file. the idea of the interface is to give more flexibility to the reader. Having a nice result screen, a setting screen, some interactions.
<Window x:Class="Beper.QCTable.Control.View.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:view="clr-namespace:Beper.QCTable.Control.View"
xmlns:viewmodel="clr-namespace:Beper.QCTable.Control.ViewModel"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Window.DataContext>
<viewmodel:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Menu -->
<Menu Grid.Row="0" FontSize="20">
<MenuItem Header="_Menu">
<MenuItem Header="_Advanced"/>
</MenuItem>
</Menu>
<!--Header-->
<StackPanel Grid.Row="1" Background="Orange">
<TextBlock FontSize="20">
Header
</TextBlock>
</StackPanel>
<!--Body-->
<Grid Grid.Row="2">
<view:TabPanel/>
</Grid>
<!--Status Bar-->
<StatusBar Grid.Row="3" FontSize="20">
<StatusBarItem>
Status
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
Focus on tab panel:
public class TabPanelViewModel
{
public ObservableCollection<TabItem> Tabs { get; set; } = new ObservableCollection<TabItem>();
public TabPanelViewModel()
{
Tabs.Add(new TabItem { Header = "One", Content = "One's content" });
Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
}
}
I cannot chare the engine code but, really, it is just a list of keys (RFID keys / EPC). This is the only public data. I want to display this list of key by group under my tabs.
Passing "this list or the entire engine reference" to the view defats the purpose of implementing the MVVM design pattern in the first place.
What you should do is to use the engine to prepare and set the state of your app/view in your view model.
The controls in the views should then bind to properties of the view model that contains, and effetively defines, the current state.

Connecting One Model to Multiple ViewModels in MVVM WPF

I am trying to accomplish a navigation bar and a content screen in .NetFramework WPF application. My goal is to implement MVVM pattern. The main objective is when I hit Users button in navigation bar, I want my Body page to render "Users" text. And when I press Actions button, body page must render "Actions" text. Navigation buttons are "Users" and "Actions". I have 3 View pages that are OperationPage, Navbar and Body. OperationPage is using Navbar and Body XAML to create view. The grid code is as following :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20*" />
<RowDefinition Height="80*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<local:Navbar/>
</Grid>
<Grid Grid.Row="1">
<local:Body/>
</Grid>
</Grid>
Navbar grid is as follows :
<UserControl.DataContext>
<vm:NavbarViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50*" />
<RowDefinition Height="25*" />
<RowDefinition Height="25*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock Text="{Binding Path=CurrentPage}" FontSize="30"/>
</Grid>
<Grid Grid.Row="1">
<Button Content="Users" Width="75"/>
</Grid>
<Grid Grid.Row="2">
<Button Content="Actions" Width="75"/>
</Grid>
</Grid>
And Finally Body grid is as following :
<Grid>
<TextBlock Text="{Binding Path=CurrentPage}" FontSize="30"></TextBlock>
</Grid>
I have created a ViewModel classes for both Navigation and Body that implements INotifyPropertyChanged. I can change the text from these classes. One of them is as following for navigation bar.
class NavbarViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyChange(string changedVar) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(changedVar));
private string _currentPage = "Navbar Data";
public string CurrentPage
{
get { return _currentPage; }
set
{
_currentPage = value;
NotifyChange("CurrentPage");
}
}
}
But I am stuck with connecting these two classes to a Singleton ViewState class. Should I again implement two-way binding with INotifyPropertyChanged to my model class or Should I follow another way? I tried implementing the INotifyPropertyChanged class again in my Model class but I could'not find a way to create connection between the ViewModel class and model.
You might solve this in a couple of ways.
The first way is to strongly-couple your ViewModels, like this.
OperationPage View
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20*" />
<RowDefinition Height="80*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<local:Navbar DataContext="{Binding NavbarViewModel}"/>
</Grid>
<Grid Grid.Row="1">
<local:Body DataContext="{Binding BodyViewModel}"/>
</Grid>
</Grid>
Then implements the OperationPageViewModel. This ViewModel should implement the INotifyPropertyChanged interface and it must have the 2 properties named NavbarViewModel and BodyViewModel of types NavbarViewModel and BodyViewModel respectively.
You might need to add to NavbarViewModel 2 events binded to Users' button clicked and Groups' button clicked in order to expose them outside and OperationPageViewModel should monitor those events (it has the instance of NavbarViewModel) then for example set CurrentPage property of BodyViewModel accordingly on user's button click.
Another solution might be to decouple ViewModels.
This solution get you better code maintenance and also let you code less than the first solution.
You need to use Message Broker design pattern https://en.wikipedia.org/wiki/Message_broker
you can implement your own Message Broker or simply use one of any MVVM Toolkit libraries that already implement it.
Most of the existing MVVM Toolkits have their own Message Broker implementation.
So no need to re-invent the whell.
For example, the MVVM Light Toolkit Message Broker is explained here: https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/june/mvvm-the-mvvm-light-messenger-in-depth#using-messages

How to load UI on navigated page(2nd page) according to the grid clicked on first page

I am working on a windows store 8.1 app, I have added Grids in MainPage.xaml using List in MainPage.xaml.cs
MainPage.xaml
<GridView Margin="20" x:Name="main" SelectionMode="None" IsItemClickEnabled="True" ItemClick="main_ItemClick">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Background="Red" Width="250" Height="200">
<Grid.RowDefinitions>
<RowDefinition Height="150"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Stretch="UniformToFill" Source="{Binding ImageLocation}"/>
<TextBlock Text="{Binding Title}" Grid.Row="1" FontSize="28" />
<TextBlock Text="{Binding SubTitle}" Grid.Row="2" FontSize="16" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
MainPage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
List<data> myList = new List<data>();
myList.Add(new data()
{
ImageLocation = #"Assets/network.png",
iName = "NetWork",
SubTitle ="Network",
Title = "Network"
});
myList.Add(new data()
{
ImageLocation = #"Assets/fb.png",
iName = "Facebook",
SubTitle = "Facebook",
Title = "Facebook"
});
main.ItemsSource = myList;
}
private void main_ItemClick(object sender, ItemClickEventArgs e)
{
Frame.Navigate(typeof(ListView));
}
I want that when someone click on any of the grids, a TextBlock in ListView page show which grid was clicked in MainPage .
This will be a challenge to explain without showing you in code, but here goes...
Hopefully you have created two pages so far. MainPage.xaml that holds your GridView. And a DetailsPage.xaml that will have the layout to show one item.
In the code-behind of MainPage.xaml, like you have in your sample code, you handle the ItemCLick of the GridView, but you want to get the Id of the item clicked, not the item itself. The reason for this is that you want to pass a string, and not a complex object.
In your handler, the event args (e) has a property called ClickedItem that will be the item you are binding to. Let's pretend it's a UserObject you are binding to. In your handler do something like this:
var user = e.ClickedItem as UserObject;
this.Frame.Navigate(typeof(DetailPage), user.Id.ToString());
So, what's happening here? Almost the same code you had before. Except you are navigating to the type of the second page instead of anything else. You are also passing in (the second argument in the Navigate method) the exact record you want to show.
Then in your DetailPage.xaml code-behind you ned to override the OnNavigatedTo method. This method is what is invoked when the Navigation framework directs to the page. It's has a NavigationPararmeter passed to it that you can use to extract the key you passed.
I think it's actually args.Parameter you want to use. You can parse it to an integer and use that to fetch the individual record you have somehow in memory in your application.
var id = int.Parse(args.Parameter);
var user = YourFactory.GetUser(id);
The reason I shifted from this is how you do it to "I think this is how it works" is because although the basic framework operates like this, most developers do not use it like this. Most developers implement something like Prism.StoreApps which introduces not only a lightweight MVVM framework, but also a sophisticated NavigationService that lets you inject parameters directly into an auto-associated view model.
But based on the simplicity of your question, try not to pay attention to that last bit. I explained the basic workflow using the in-box framework. It works just fine, and it will get the job done. When you are ready to write a more advanced implementation you can investigate Prism.StoreApps
More info: http://msdn.microsoft.com/en-us/library/windows/apps/xx130655.aspx
Best of luck!

How do I load controls in different ContentControls of a Shell using CaliburnMicro

By default when you use "ActivateItem(new Control());" your control is loaded into a ContentControl which with the name ActiveItem, fro example. . If I have multiple content controls on my page how would I load controls into them whilst retaining the ability to use the default functionality of being able to load controls into the the active item control.
for example I want to have a login control to be loaded into the Login ContentControl, and when a user successfully login I want a new control to be loaded into the ActiveItem ContentControl.
Thanx in advance.
If the ViewModel that gets binded to the UI contains a property with the name that matches a content control. The Content control view automatically gets resolved the the view supported by this property, provided this property itself is a ViewModel type and has been registed with Ioc container. For example
<ContentControl x:Name="LoginStatus"></ContentControl>
If there is a property LoginStatus on the main ViewModel (LoginStatus property itself is a ViewModel). The content control would correctly get rendered with the appropriate view.
This is an old question, but in case anyone is having the same issue, here is my solution:
Your main window that contain both (or even more than two) of your User Controls must be inherited from Caliburn.Micro.Conductor<Screen>.Collection.AllActive;
Your User Controls must be inherited from Caliburn.Micro.Screen;
You must also keep naming conventions in mind. If you use MenuUC as the name of a ContentControl in your View, also create a property named MenuUC in your ViewModel;
Initialize your UserControl as I do in Constructor;
Now you can use ActivateItem(MenuUC) and DeactivateItem(MenuUC) everywhere in your code. Caliburn.Micro automatically detects which one you want to work with.
Example XAML View code:
<Window x:Class="YourProject.Views.YourView"
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"
Title="YourViewTitle" Width="900" Height="480">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Menu Side Bar -->
<ContentControl Grid.Row="0" Grid.Column="0" x:Name="MenuUC" />
<!-- Panel -->
<Border Grid.Column="1" Grid.RowSpan="2" BorderThickness="1,0,0,0" BorderBrush="#FF707070" >
<ContentControl x:Name="PanelUC" />
</Border>
</Grid>
</Window>
Example C# ViewModel code:
class YourViewModel : Conductor<Screen>.Collection.AllActive
{
// Menu Side Bar
private MenuUCViewModel _menuUC;
public MenuUCViewModel MenuUC
{
get { return _menuUC; }
set { _menuUC = value; NotifyOfPropertyChange(() => MenuUC); }
}
// Panel
private Screen _panelUC;
public Screen PanelUC
{
get { return _panelUC; }
set { _panelUC = value; NotifyOfPropertyChange(() => PanelUC); }
}
// Constructor
public YourViewModel()
{
MenuUC = new MenuUCViewModel();
ActivateItem(MenuUC);
PanelUC = new FirstPanelUCViewModel();
ActivateItem(PanelUC);
}
// Some method that changes PanelUC (previously FirstPanelUCViewModel) to SecondPanelUCViewModel
public void ChangePanels()
{
DeactivateItem(PanelUC);
PanelUC = new SecondPanelUCViewModel();
ActivateItem(PanelUC);
}
}
In the above example, ChangePanels() acts as a method to load new User Control into your ContentControl.
Also read this question, it might be help you further.
You should have a look at Screen Conductors. See here.

Improving binding performance in WPF?

I realize this question could be boiled down to "Why is my code so slow?" but I'm hoping to get more out of that. Let me explain my code.
I have a class that implements INotifyPropertyChanged in order to do binding, and that class looks similar to this:
public class Employee : INotifyPropertyChanged
{
string m_strName = "";
string m_strPicturePath = "";
public event PropertyChangedEventHandler PropertyChanged;
public string Picture
{
get { return this.m_strPicturePath; }
set { this.m_strPicturePath = value;
NotifyPropertyChanged("Picture"); }
}
public string Name
{
get { return this.m_strName; }
set { this.m_strName = value;
NotifyPropertyChanged("Name");
}
}
private void NotifyPropertyChanged(String pPropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(pPropName));
}
}
}
In my XAML I've created a DataTemplate that binds to this object:
<DataTemplate x:Key="EmployeeTemplate">
<Border Height="45" CornerRadius="0" BorderBrush="Gray" BorderThickness="0" Background="Transparent" x:Name="bordItem">
<Grid Width="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Name}" VerticalAlignment="Center" Padding="10" HorizontalAlignment="Stretch" FontWeight="Bold" FontSize="20"/>
<Image Grid.Column="1" Source="{Binding Path=Picture}"></Image>
</Grid>
</Border>
</DataTemplate>
and then put this template on a ListBox:
<ListBox x:Name="lstEmployees" ItemTemplate="{DynamicResource EmployeeTemplate}" VirtualizingStackPanel.VirtualizationMode="Recycling" VirtualizingStackPanel.IsVirtualizing="True"></ListBox>
So in code it's set as:
lstEmployees.ItemsSource = this.m_Employees;
the "m_Employees" list gets hydrated at app startup from a database, and then after that happens I set the above line of code. The ListBox is on a TabControl.
Now, my actual problem: My "m_Employees" list is returning about 500+ employees from the database, so the collection is slightly big. I get a performance hit in WPF only when the application first starts up and someone navigates to that tab with the ListBox on it. The UI freezes for about 3 seconds, but only when the app first starts up - afterwards it's fine.
Could this be because:
The code has to hit the hard drive to go find the image of each employee?
I am doing Virtualizing incorrectly?
EDIT
WPF is doing the rendering using my DataTemplate once, only when someone navigates to that TabControl, and is suddenly trying to draw 500+ employee items? If so, is there any way to "preload" the ListView in WPF?
Any other suggestions for improving the above would be apprecated. Thanks for reading and for any advice ahead of time.
-R.
Wrap m_Employees with a public
property (Employees)
Instead of setting your ItemsSource in the code like you do, set it with Binding and set IsAsync to
True.
ItemsSource="{Binding Empolyess, IsAsync=True}"
You can also assign the Binding in the code.
Hope this helps.
The perf of your query is definitely suspect. If you want it to perform better, you can do any number of lazy initialization techniques to get it to run faster.
The easiest option would be to start with an empty enumeration, and only populate it at a later time.
The obvious way to do this would be to add a "Query" or "Refresh" button, and only freeze up the app when the user clicks it.
Another simple option is to queue a background task/thread to do the refresh.
If you are more concerned about consistent perf/super-responsive UI, then you should try to do more granular queries.
I am not sure if WPF handles virtualization of the items (only pulls from the enumeration when each item comes into view), but if it does, you could do paging/yield returns to feed ItemsSource.
If WPF just grabs the whole enumeration at once, you could still do smaller lazy-eval/paging, if you can determine which items are in view. Just populate the object with "zombie" items, and when they come into view, perform the query, and update the properties on the individual item.

Categories

Resources