Difficulty updating UI when contents of ObservableCollection is changed? - c#

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. :)

Related

How to DataBind a label or a textBlock in Datagrid Cell?

Hello Guys I hope yall doing great!
I have a problem with a datagrid, I want to put this in datagrid cell for example : "/100" which '100' is the quantity in stock in my database using entity framework, I want to do this so the user tap how many items he wants in a textbox besides the label/textBlock like this :
DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox materialDesign:HintAssist.Hint="0"/>
<TextBlock Text="{Binding ElementName=productQuantityStock}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
I hope you guys understand what I want to do, and please mind my bad English
In the code-behind you'll need to bind a context. The context will need a property and a field so the the value can be updated. You should also make use of a method that notifies the view that the property has been updated.
The code behind will look something like this.
public class DataContextOfView
{
private int _productQuantityStock;
// Because you'll be working with values of type int you should make it an int
public int ProductQuantityStock
{
get { return _productQuantityStock;}
set { if(_productQuantityStock != value)
{
_productQuantityStock = value
// notify that the value of the property has changed.
OnPropertyChanged(nameof(ProductQuantityStock));
}
}
}
}
The code in the view should have a reference to the property
DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox materialDesign:HintAssist.Hint="0"/>
<TextBlock Text="{Binding ProductQuantityStock, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>

Why does my dropdown feel so clunky?

I have a XAML UserControl embedded in a WinForms/WPF Interop ElementHost control. The control is pretty simple - it's just a dropdown with a button - here's the entire markup:
<UserControl x:Class="Rubberduck.UI.FindSymbol.FindSymbolControl"
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:Rubberduck.UI.FindSymbol"
mc:Ignorable="d"
d:DesignHeight="27" d:DesignWidth="270">
<UserControl.Resources>
<local:DeclarationImageConverter x:Key="DeclarationImageConverter" />
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="local:FindSymbolControl.GoCommand"
Executed="CommandBinding_OnExecuted"
CanExecute="CommandBinding_OnCanExecute"/>
</UserControl.CommandBindings>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<ComboBox IsEditable="True"
ItemsSource="{Binding MatchResults}"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
IsTextSearchCaseSensitive="False"
IsTextSearchEnabled="True"
TextSearch.TextPath="IdentifierName">
<ComboBox.ItemTemplate>
<DataTemplate DataType="local:SearchResult">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Image Height="16" Width="16" Margin="2,0,2,0" Source="{Binding Declaration, Converter={StaticResource DeclarationImageConverter}}" />
<TextBlock Margin="2,0,2,0" Text="{Binding IdentifierName}" FontWeight="Bold" MinWidth="140" />
<TextBlock Margin="2,0,2,0" Text="{Binding Location}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Grid.Column="1"
Command="local:FindSymbolControl.GoCommand">
<Image Height="16" Source="pack://application:,,,/Rubberduck;component/Resources/arrow.png" />
</Button>
</Grid>
</UserControl>
The problem is that it doesn't work reliably, and far from instinctively.
If I type something in the box that actually matches an item, nothing happens until I manually select that item in the dropdown. Like here, I typed "sleepD", the box autocompleted to "sleepDelay", but the command is still disabled:
Once I've selected the item in the dropdown, the command button gets enabled as expected (although the image on the button doesn't show up grayed-out when the button is disabled, so it's not exactly as obvious as I intended it to be).
(the screenshot isn't really showing it, but there's only 1 match for that search)
If I click the button at that point, it works as expected. The problem is that if I make a new selection from the dropdown after that, the text box gets cleared instead of displaying the item I selected, and there's a weird delay during which the box is displaying what appears to be selected whitespace - this only seems to happen when the previous selection was made after selecting a value in the dropdown while the search text matches multiple entries, like "Sleep" above.
After the box got cleared, I can make a new selection from the dropdown and it will work as expected (except the VBE won't actually activate the CodePane I'm setting the selection to, but that's a separate issue).
The command implementation simply raises a Navigate event that passes a Declaration to the code that owns the VM instance.
The Search method, for which I need to add a .Take(50) after the .Select, to limit the number of returned results and perhaps reduce the lag a bit:
private void Search(string value)
{
var lower = value.ToLowerInvariant();
var results = _declarations.Where(
declaration => declaration.IdentifierName.ToLowerInvariant().Contains(lower))
.OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())
.Select(declaration => new SearchResult(declaration));
MatchResults = new ObservableCollection<SearchResult>(results);
}
private string _searchString;
public string SearchString
{
get { return _searchString; }
set
{
_searchString = value;
Search(value);
}
}
private SearchResult _selectedItem;
public SearchResult SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
private ObservableCollection<SearchResult> _matchResults;
public ObservableCollection<SearchResult> MatchResults
{
get { return _matchResults; }
set { _matchResults = value; OnPropertyChanged(); }
}
}
There's also an IValueConverter involved, that takes the Declaration in the SearchResult and switches on the declaration's DeclarationType enum to return a pack uri that points to the .png image to use in the dropdown list.
Aaah found it. It was all in the XAML.
Right here:
Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
That line doesn't belong there; binding the TextSearch.Text property instead...
TextSearch.Text="{Binding SearchString, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
Makes it all work as intended. No glitch, no lag. Well there is a lag when I first drop the dropdown, but that's another issue.
Lesson learned: when TextSearch is enabled on an editable combobox, don't bind the Text property, unless you want weird behavior.

Is there a way to to Indent certain items in a list view using c# for a windows 8 store app?

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.

How to get content of a InlineEditorTemplate to update when its underlying PropertyValue updates

I have a collection of objects on a custom WF4 activity that the user can edit in a designer. I'm using a DialogPropertyValueEditor to popup a editor dialog from the PropertyGrid which can be used add\edit\remove items in the collection.
In the constructor of the DialogPropertyValueEditor I set the InlineEditorTemplate to a simple DataTemplate that contains a TextBlock that displays the number of items currently in the collection.
public class DataSourceFieldMappingsPropertyEditor : DialogPropertyValueEditor
{
public DataSourceFieldMappingsPropertyEditor()
{
base.InlineEditorTemplate = EditorDataTemplatesResources.GetGenericListDialogDataTemplate();
}
public override void ShowDialog(PropertyValue propertyValue, System.Windows.IInputElement commandSource)
{
//display dialog
}
}
The DataTemplate looks as follows:
<DataTemplate x:Key="editorDataTemplatesGenericListDialog">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Path=Value,
Converter={StaticResource ResourceKey=GenericListConverter}, Mode=OneWay, pdateSourceTrigger=PropertyChanged}" />
<sapp:EditModeSwitchButton Grid.Column="1" TargetEditMode="Dialog" />
</Grid>
The GenericListConverter simply looks at the collection and displays "0 Items", "5 Items" etc.
My question is: how do I get the InlineEditorTemplate to automatically update itself when the underlying collection changes. The DataContext for the InlineEditorTemplate is PropertyValue, but somehow the binding doesn't hook into the PropertyChanged event.
I have to click of the activity in the designer and come back to it to see the count display updated.

WPF ListBoxItem(s) losing programatically set styles after scroll

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.

Categories

Resources