So, I have a ListBox which is bound to a list of business objects, using a DataTemplate:
<DataTemplate x:Key="msgListTemplate">
<Grid Height="17">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding MaxWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Foreground="Silver" Text="{Binding SequenceNo}" />
<TextBlock Grid.Column="1" Text="{Binding MessageName}" />
</Grid>
</DataTemplate>
<ListBox Name="msgList"
Grid.Column="0"
ItemTemplate="{StaticResource msgListTemplate}"
SelectionChanged="msgList_SelectionChanged"
VirtualizingStackPanel.IsVirtualizing="True"
ScrollViewer.HorizontalScrollBarVisibility="Hidden">
</ListBox>
Sometime after binding, I want to colour certain items in the list to distinguish them from the others. I do this on a background thread:
if(someCondition)
{
msgList.Dispatcher.BeginInvoke(new Fader(FadeListItem), DispatcherPriority.Render, request);
}
delegate void Fader(GMIRequest request);
void FadeListItem(GMIRequest request)
{
ListBoxItem item =
msgList.ItemContainerGenerator.ContainerFromItem(request) as ListBoxItem;
if(item!=null)
item.Foreground = new SolidColorBrush(Colors.Silver);
}
This all works fine, and some list items are greyed out as expected. However, if I scroll such that the greyed items are no longer shown, then scroll back again to where they were, they are no longer silver, and have returned to the default black foreground.
Any idea why this is, or how to fix it? Is it because I have set IsVirtualizing to true? The listbox typically contains many items (20,000 is not uncommon).
Is it because I have set IsVirtualizing to true? The listbox typically contains many items (20,000 is not uncommon).
You nailed it - the item you set the foreground color on is getting trashed once the user scrolls away.
While you've got the right general idea, the way you're going about this is a very un-WPFy way to do this - one better way to do this is to have a bool DP in your business object class (or have the BO implement INotifyPropertyChanged), then bind the bool to the Foreground color via a custom IValueConverter that returns (isTrue ? whiteBrush : greyBrush).
Since you may not want to / may not be able to modify your business object to support INotifyPropChanged, this is the reason for the M-V-VM pattern - create a class that wraps the object that is a DependencyObject and exposes just the properties you're interested in displaying.
Related
I have a combobox bound to an observablecollection as:
<ComboBox
HorizontalAlignment="Left" Margin="586,51,0,0" VerticalAlignment="Top" Width="372"
SelectedItem="{Binding PrimaryInsurance.SelectedInsurance}"
ItemsSource="{Binding PrimaryInsurance.AllPatientInsurance}"
ItemTemplate="{StaticResource InsuranceTemplate}" />
The observablecollection itself is defined as:
private ObservableCollection<Insurance> _allPatientInsurance;
public ObservableCollection<Insurance> AllPatientInsurance
{
get { return _allPatientInsurance; }
set { if (_allPatientInsurance == value) return; _allPatientInsurance = value; OnPropertyChanged("AllPatientInsurance"); }
}
Now Insurance encapsulates data downloaded from the database an adds INotifyPropertyChanged as:
public string CompanyName
{
get { return insurance_View.Companyname; }
set { if (insurance_View.Companyname == value) return; insurance_View.Companyname = value; OnPropertyChanged("CompanyName"); }
}
Where insurance_View is the raw data record downloaded from the database.
All is good.
However, in an "UnDo" operation, I would like to replace the edited insurance_View record with its original record like:
internal void UnDo()
{
insurance_View = (Insurance_View)pre_edit_Insurance_View.Clone();
}
Although the edited version of insurance_View is correctly changed back to its original form, the display is not being updated. Futhermore, replacing the edited version of insurance with the original version of insurance in the ObservableCollection like:
AllPatientInsurance.Remove(Old Insurance);
AllPatientInsurance.Add(New Insurance);
Destroys all the bindings and displays a blank record.
So, what is the best way to update the display when the contents of Insurance is changed without destroying the Insurance Object? Is there a better way?
Edit #1. Just to be clear, I am trying to replace the data record within the Insurance object where it is the Insurance object that is bound to the display. I am not wanting to replace the entire collection being displayed. The only thing that comes to mind is to replace each value of the edited record with its original value, but this seems tedious so I am hoping for a better method.
Edit #2. Is there any way to trigger the Insurance setters when the encapsulated
Insurance_View record is changed?
Edit #3. The insurance template:
<!--DataTemplate for the Combobox Insurance List-->
<DataTemplate x:Key="InsuranceTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="14" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding XNotInEffect}" Grid.Column="0" />
<TextBlock Text="{Binding CompanyName}" Grid.Column="1"/>
<TextBlock Text="{Binding EffectiveDateFrom}" Grid.Column="2"/>
<TextBlock Text="--" Grid.Column="3" />
<TextBlock Text="{Binding EffectiveDateTo}" Grid.Column="4" />
<TextBlock Text="{Binding InsuranceType}" Grid.Column="5"/>
</Grid>
</DataTemplate>
Also, please note that simply removing then adding the same Insurance object results in its disappearance from the combobox drop down. Example:
AllPatientInsurance.Remove(SelectedInsurance);
AllPatientInsurance.Add(SelectedInsurance);
TIA
I suppose your InsuranceTemplate has bindings to the Insurance' properties such as CompanyName and therefore listens to property changed events from the Insurance instance (the template's DataContext). So because the undo operation doesn't change the properties by calling the corresponding setters (but by changing the insurance_view) you have to manually trigger the property changed events (OnPropertyChanged("CompanyName") and so on) of every changed property after the undo operation to notify the view.
Track the old insurance in its own observable collection. In your undo, you can assign the old collection to AllPatientInsurance, and let the property do the heavy lifting.
//initialize this elsewhere as appropriate
private ObservableCollection<Insurance> _oldPatientInsurance;
internal void UnDo()
{
insurance_View = (Insurance_View)pre_edit_Insurance_View.Clone();
AllPatientInsurance = _oldPatientInsurance;
}
This code does not work because the SelectedInsurance becomes null the moment it is removed from the collection:
AllPatientInsurance.Remove(SelectedInsurance);
AllPatientInsurance.Add(SelectedInsurance);
However, if a reference to the SelectedInsurance is kept then it can be added back as so:
SelectedInsurance.Reset();
var x = SelectedInsurance;
AllPatientInsurance.Remove(SelectedInsurance);
AllPatientInsurance.Add(x);
SelectedInsurance = x;
Where Reset() is
internal void Reset()
{
insurance_View = (Insurance_View)pre_edit_Insurance_View.Clone();
}
and the last line sets the combobox back to the initially selected item.
So simple. :)
Basically, I have a list of items and sub items (all in one list). I want to indent certain specific items that are actually sub items. Is there a function or property that I can use to do this? I've tried googling for it, and even searching for it here on stack overflow - but no success.
NOTE: I'm using C# and XAML for a windows 8 store application. Not a wpf app (the documentation and code differ sometimes).
EDIT:
Here's what my XAML looks like:
<ListView
x:Name="ListView"
AutomationProperties.AutomationId="ListView"
AutomationProperties.Name="items"
TabIndex="1"
Grid.Row="1"
Margin="-10,-10,0,0"
Padding="20,0,0,45"
IsSwipeEnabled="False"
SelectionChanged="ListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="10,0,0,0">
<TextBlock x:Name="Item" Tag="{Binding ID}" Text="{Binding Name}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="Wrap" MaxHeight="40" FontSize="14" FontFamily="Global User Interface"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The listview is binded to an observablelist of objects.
To Expand further on another answer here... The suggested route was to add a property to the underlying class (in this case, MyClass) and bind the ItemsSource for the ListView to a list of these objects.
public class MyClass
{
public bool IsSubItem { get; set; }
}
// Elsewhere in your code, you would need a list of these object
public ObservableCollection<MyClass> MyList { get; set; }
You would also need a Converter class, which is very easy to setup:
You'd need a converter class; converters are really easy once you get the hang of them. A simple example for this scenario would be:
public class BoolToMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
//Get the passed in value and convert it to a boolean - the as keyword returns a null if we can't convert to a boolean so the ?? allows us to set a default value if we get a null instead
bool isSubItem = (value as bool?) ?? false;
// If the item IS a sub item, we want a larger Left Margin
// Since the Margin Property expects a Thickness, that's what we need to return
return new Thickness(isSubItem == true ? 24 : 12, 0, 0, 0);
}
// This isn't necessary in most cases, and in this case can be ignored (just required for the Interface definition)
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Once you get this setup, you'll need to add this to your XAML as a resource:
// Add a reference to your namespace if it isn't already in your XAML
xmlns:local="using:MyNamespace"
// You'll also need to add a static resource
<Page.Resources>
<local:BoolToMarginConverter x:Key="BoolToMarginConverter" />
</Page.Resources>
// And then lastly, you'll need to update your ListView XAML to use the new information
<ListView
x:Name="ListView"
ItemsSource={Binding MyList}
<!-- Other Properties removed for space -->
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="1">
<!-- Other info removed for space -->
<StackPanel Grid.Column="0" Margin="{Binding Path=IsSubItem, Converter={StaticResource BoolToMarginConverter}}">
<!-- Other info removed for space -->
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If you just want to indent the text of the sub-items you could change the margin of the text block or stack panel.
To do this you would do the following:
Create a class for the items you will be adding to the list view
Add to the class, an IsSubItem property
Create an observable collection of these items and bind them to your list view source.
In the ListView template, bind the stack panel or text block margin to the IsSubItem property using a converter to convert the IsSubItem boolean into the appropriate margin.
I have a class like this:
public class NQWell : ObservableObject, ICloneable
{
public string Name { get; set; }
public ObservableCollection<KeyValuePair<int, double>> ResultsObservableCollection { get; set; }
}
ObservableCollection<NQWell> wells { get; set; }
I want to show this object in WPF (ListView, Grid, something..)
It should look like this:
ColumnHeaders:
Well 260 280
Data:
A1 0.2 0.3
A2 0.1 0.4
A3 0.6 0.5
The "Well" header comes from the name of the property, and the "260" and "280" are actually the keys from the observable collection.
The problem is, how do I normalize the object?
One possible solution that I thought would be to do it in itemscontrol for the first observable collection (wells) and show every property consequently in textblocks, and when it comes to the ObservableCollection> ResultsObservableCollection, I create another ItemsControl that walks that collection and shows it, and so on.
Is there a better way? My thoughts were, I do a ListView with GridView, and then create anonymous types for the outer properties, and another anonymous type for the inner observable collection, which I merge in a new object at the end. But this is somehow too much work. Has somebody any better propsals?
Your data object is not very complex at all... it just consist of a string name and a collection, so there should be no problem with displaying it. First, define a DataTemplate for your class... clearly, you'll need a collection container and a TextBlock or similar:
<DataTemplate DataType="{YourPrefix:NQWell}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}" />
<ListBox Grid.Row="1" ItemsSource="{Binding ResultsObservableCollection}" />
</Grid>
</DataTemplate>
As I did not specify an x:Key value for this DataTemplate, it will be implicitly applied to all instances of your custom class. Now you've got a collection of these data types, so you'll need another container control:
<ListBox ItemsSource="{Binding wells}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Key}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now every item in this collection will be rendered using our DataTemplate, including a Name Label and a ListBox for it's items. The last part of this is to declare another DataTemplate to define what your KeyValuePair<int, double> items should look like. So as you can see, by breaking this problem down and then building this code up bit by bit, it is easy to accomplish.
Background:
I have a ListBox containing items defined by DataTemplates. Right now, if an object in the list has the property IsEditable set to true, the item's property information will be displayed inside of textboxes (via DataTemplate change), instead of textblocks (so the user can edit the content of that list item)
IsEditable is toggled on/off by a button inside of each list item. I have been told that we need to keep the state of all objects consistent, which means I can't just rebind the ItemsSource and lose everything.
Currently, I'm using this to re-render:
this.lbPoints.Dispatcher.Invoke(DispatcherPriority.Render, new Action(() => { }));
Question:
The aforementioned code snippet KIND OF does its job. By "kind of", I mean, it does eventually cause my data to become re-rendered, but only when I scroll to the bottom of the list and then scroll back up to the item i'm trying to re-render.
1) How can I re-render the data immediately without having to scroll around to get it to show up?
The guys commenting are right that you're going about this the wrong way... there is rarely a need to force a ListBox to re-render. You're probably causing yourself some additional grief trying to switch the DataTemplates (although it is possible). Instead of that, think about data binding the TextBox.IsReadOnly property to your IsEditable property:
<TextBox IsReadOnly="{Binding IsEditable}" Text="{Binding Text}" />
Another alternative is to use a BooleanToVisibilityConverter to show a different Grid in your DataTemplate when your IsEditable property is true. Unfortunately, that Converter doesn't have an inverse operation, so you could create an IsNotEditing property to bind to the Grid in the DataTemplate that is originally displayed. I'm not sure if that's clear... see this example:
<DataTemplate DataType="{x:Type YourPrefix:YourDataType}">
<Grid>
<Grid Visibility="{Binding IsNotEditing, Converter={StaticResource
BooleanToVisibilityConverter}}">
<!-- Define your uneditable UI here -->
</Grid>
<Grid Visibility="{Binding IsEditing, Converter={StaticResource
BooleanToVisibilityConverter}}">
<!-- Define your editable UI here -->
</Grid>
</Grid>
</DataTemplate>
You could also define your own BooleanToVisibilityConverter class that has an IsInverted property, so that you can just use the one IsEditing property. You'd need to declare two Converters still, like this:
<Converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<Converters:BoolToVisibilityConverter x:Key="InvertedBoolToVisibilityConverter"
IsInverted="True" />
Then your XAML would be like this:
<Grid Visibility="{Binding IsEditing, Converter={StaticResource
InvertedBoolToVisibilityConverter}}">
<!-- Define your uneditable UI here -->
</Grid>
<Grid Visibility="{Binding IsEditing, Converter={StaticResource
BoolToVisibilityConverter}}">
<!-- Define your editable UI here -->
</Grid>
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.