WPF Combobox binding: can't change selection - c#

After wasting hours on this, following on the heels of my Last Problem, I'm starting to feel that Framework 4 is a master of subtle evil, or my PC is haunted.
I have three comboboxes and a textbox on a WPF form, and I have an out-of-the-box Subsonic 3 ActiveRecord DAL.
When I load this "edit record" form, the comboboxes fill correctly, they select the correct items, and the textbox has the correct text. I can change the TextBox text and save the record just fine, but the comboboxes CANNOT BE CHANGED. The lists drop down and highlight, but when you click on an item, the item selected stays the same.
Here's my XAML:
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Asset</TextBlock>
<ComboBox Name="cboAsset" Width="180"
DisplayMemberPath="AssetName"
SelectedValuePath="AssetID"
SelectedValue="{Binding AssetID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Status</TextBlock>
<ComboBox Name="cboStatus" Width="180"
DisplayMemberPath="JobStatusDesc" SelectedValuePath="JobStatusID"
SelectedValue="{Binding JobStatusID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Category</TextBlock>
<ComboBox Name="cboCategories" Width="180"
DisplayMemberPath="CategoryName"
SelectedValuePath="JobCategoryID"
SelectedValue="{Binding JobCategoryID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Reason</TextBlock>
<TextBox Name="txtReason" Width="380" Text="{Binding Reason}"/>
</StackPanel>
Here are the relevant snips of my code (intJobID is passed in):
SvcMgrDAL.Job oJob;
IQueryable<SvcMgrDAL.JobCategory> oCategories = SvcMgrDAL.JobCategory.All().OrderBy(x => x.CategoryName);
IQueryable<SvcMgrDAL.Asset> oAssets = SvcMgrDAL.Asset.All().OrderBy(x => x.AssetName);
IQueryable<SvcMgrDAL.JobStatus> oStatus = SvcMgrDAL.JobStatus.All();
cboCategories.ItemsSource = oCategories;
cboStatus.ItemsSource = oStatus;
cboAsset.ItemsSource = oAssets;
this.JobID = intJobID;
oJob = SvcMgrDAL.Job.SingleOrDefault(x => x.JobID == intJobID);
this.DataContext = oJob;
Things I've tried:
Explicitly setting IsReadOnly="false" and IsSynchronizedWithCurrentItem="True"
Changing the combobox ItemSources from IQueryables to Lists.
Building my own Job object (plain vanilla entity class using INotifyPropertyChanged).
Every binding mode for the comboboxes.
ItemsSource="{Binding}"
The Subsonic DAL doesn't implement INotifyPropertyChanged, but I don't see as it'd need to for simple binding like this. I just want to be able to pick something from the dropdown and save it.
Comparing it with my last problem (link at the top of this message), I seem to have something really wierd with data sources going on. Maybe it's a Subsonic thing?
EDIT: For some reason the set accessor is hit only on the AssetID property and only the first time. WPF is now heading for WTF :)
EDIT 2: You gotta be kidding me- I've removed the binding (ie it only has a displaymemberpath, a valuememberpath and an itemssouce) and it's STILL doing it! It accepts your first selection, and then won't change.

WPF Combo Boxes will not change the selected item if the currently selected item and the item that was just selected are considered equal by the object.Equals() method called on the newly selected object (i.e newlyslected.Equals(previoslySelected) ).
Overriding the Equals method on the class your binding the combobox items, should resolve the issue your are seeing.

I've narrowed it down to the Subsonic objects used as ComboBoxItems.
If you create a new class that uses exactly the same code as the relevant parts of the Subsonic one, it works.
If you use POCOs/datatables for the combos and Subsonic for the record being edited, it works.
But if you use Subsonic for both, it doesn't.
I had hoped to extend the subsonic objects and not have to code a full-blown BLL tier. Looks like I'm faced with doing that or throwing out Subsonic for the DAL. I might post a more specific question for the Subsonic folks.
Many thanks to all who contributed.

Old topic but I had the same problem and difficulty finding solution. This might help someone else.
Clue is above in WPF not detecting a different item has been seleted by user. (Symptom - event ComboBox_SelectionChanged only fires on first selection)
My scenario - lookup combo populated from IList built from a DISTINCT query. In this case the result of using NHibernate ICriteria.SetResultTransformer which only returns SOME fields, importantly NOT including the unique entity ID.
Solution - loop thru' IList after retrieval and give each entity a unique ID. WPF sees them as individuals and behaves appropriately.
Its only a value lookup - its the value content I was after.
The 'temporary' entities are never persisted. In this case it was a better approach than messing with overriding the object's Equals method for the sake of a simple GUI issue. An alternative would be to just copy or tranform the list into a format where WPF uses the value field to determine 'difference'...

Sounds like the field is somehow readonly, or that your change isn't being persisted. After the binding sets the new value, it will re-read the property to ensure that it was actually changed. If your property returns the old value, then it'll be re-selected in the combo box, giving the appearance that the value never changed.
I don't know that DAL, but can you step through the property setter code? You might also have an issue with type conversion.
EDIT reading your comment about the red rectangle -- it sounds as though your property (or something to do with the binding) is raising an exception. Unless, of course, you're using data validation in your UI. You might turn 'Break on all exceptions' in the debugger's settings, assuming you're using Visual Studio.
EDIT 2 You should check the VS Output pane for any error messages related to binding. You can also read this blog post which gives more info on debugging bindings.

It's hard to tell from a small sample of your code but try commenting out the line:
//this.DataContext = oJob;
and see if this helps.
Setting the DataContext and ItemsSource might be causing a conflict.

Did you write any global style for your combo box which may have a bug or something missing? Or are you using pure default styles for your combobox? Try removing any default styles applied.
Are you wiring up any events? If your code hooks up for event like PreviewMouseLeftButtonUp and marks event as handled then probably combobox may ignore and wont select anything.

Related

Access Voilation (Memory Leak) in UWP C#

I am working on UWP app that has Order Class, which bind to UWP UI. It was all working fine then it start to crash at point with Access Violation error. I debug and found that I have a TextBox in XAML, that bind to Order Class's one of property. [there are several other in exact same manner from same class]. Now when I update the property of order by calculating value (it is been a float value) it is set to update on UI, it update other fields well but crash when I try to set this one field with memory violation. I have 10 different field that bind fine in same function to same UI from same object,
<TextBox x:Name="Margin" IsReadOnly="True" MinWidth="100" MaxWidth="120" MaxLength="10" Header="Margin (£)" Padding="5"
Margin="5,5,5,0" Text="{x:Bind ViewModel.SelectedOrder.Margin, Mode=TwoWay}" RelativePanel.Below="GridOrderItems"
RelativePanel.RightOf="Parcel" />
<TextBox x:Name="txtMarginP" IsReadOnly="True" MinWidth="100" MaxWidth="120" MaxLength="5" Header="Margin (%)" Padding="5"
Margin="5,5,5,0" Text="{x:Bind ViewModel.SelectedOrder.MarginP, Mode=TwoWay}"
RelativePanel.RightOf="Margin" RelativePanel.Below="GridOrderItems" />
The field above pass through without error, but field txtMarginP fails with error.
private async Task UpdateCart(float shippingprice = 0)
{
...
ViewModel.SelectedOrder.Margin = calculatedMargin;
ViewModel.SelectedOrder.MarginP = calculatedMarginP;
...
}
This is the function call that generate error. I calculate margin (as float) and pass. It used to work. Then it start to fail, while I work on other screen not related to it, and order class and this UI is unchanged.
Excat error is Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt. Which comes in Orderdetail.g.cs file [that is autogenerated by XAML compiler. Object value get set properly.
I try to use DispatcherQueue to set value in my object but it doesn't solve the problem. Since nothing is knowningly change I believe either some compiler setting changed in one of update to Visual Studio or something else. But I cannot figure it out nor can I debug it to solve it. Any Idea is appreciated. Thank you.
EDIT ---
After some code changes [to rearrange there call in symmetric order], I nail down to situation where application stop crashing, if I change my ObservableCollection to List, but then it do not bind, and item is not visible even if I change I assign the Grid.Itemsource
It turns out that I was binding some data in my combobox in my listview. Since I have to bind that Combobox multiple time when we reload product. it was causing the XAML to leak memory and go crazy. I have to remove ComboBox from ListView and replace it with AutoSuggestBox or [depending on your workflow] or limit the number of item you can load in combobox. I cannot limit them as I have around 700 record to bind... with 2-3 rows they duplicated to 1400-2100 element to manage, with observable for all.
Rewriting some major portion to avoid it.

How to preview what a XAML control with bound values looks like

I'm writing an app in XAML, and I'm using binding for getting values to the UI layer. I'd like to see what my control will look like while making changes to the XAML, but because the data values are bound, many areas show up as blank (which, in turn, messes up the relative layout).
Is there any way to give XAML values to use for rendering the control review without replacing the Binding directives?
You can also use design time data to see how your xaml works. You just need to add new class that will be treated as design time view model. Its more elegant way to test xaml at design time.
Maybe you can set TargetNullValue or FallbackValue property in your binding, example:
<TextBlock Text="{Binding NotExsitOrNullPropertyName, TargetNullValue=SomeDefaultValue, FallbackValue=SomeDefaultValue}" ></TextBlock>
Hope it hepls.

Accessing a component of a column in a DataGrid

I'm a Java developer by profession & was given some tasks in .NET as a pilot project.
It's a small invoicing application which needs to be developed with WPF & EntityFramework.
One of my tasks consist of showing a list of invoices in a window and upon clicking "edit" for any invoice, I should show the details of that invoice along with the invoice items that are assigned to that invoice.
Following is my XAML code fragment of showing invoice items.
<DataGrid x:Name="ProductGrid" AutoGenerateColumns="False" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch" ColumnWidth="*" Height="464" VerticalAlignment="Top" Margin="444,16,10,0" CanUserAddRows="false">
<DataGrid.Columns>
<DataGridTemplateColumn Width="55" Header="Selected">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Margin="2,0,2,0" HorizontalAlignment="Center" VerticalAlignment="Center"
Checked="Product_Selected" Unchecked="Product_Deselected" IsChecked="{Binding Path=selected}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="60" Header="Quantity">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<xctk:IntegerUpDown x:Name="UPDOWN" Increment="1" Minimum="0" HorizontalAlignment="Center" ValueChanged="Quantity_Changed"
VerticalAlignment="Center" Width="50" Value="{Binding productQuantity, Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Product Name" Width="250" Binding="{Binding Path=productName}"/>
<DataGridTextColumn Header="Weight" Binding="{Binding Path=productWeight}"/>
<DataGridTextColumn Header="Size" Binding="{Binding Path=productSize}"/>
<DataGridTextColumn Header="Sale price" Binding="{Binding Path=productSalePrice}"/>
</DataGrid.Columns>
</DataGrid>
Now, what I need to achieve is that when I select a checkbox, the code behind should automatically increase the value of the IntegerUpDown component to 1. Also if I deselect a checkbox, the code behind should automatically reset the value of the IntegerUpDown component to 0.
Following is my code fragment for Product_Selected event.
private void Product_Selected(object sender, RoutedEventArgs e)
{
var currRow = ProductGrid.CurrentItem; // Current row
InvoiceItemsDTO sel = (InvoiceItemsDTO)currRow; // Current row DTO OBJECT
if (sel != null)
{
if (sel.productQuantity == 0) // The user is trying to assign a new item to the invoice
{
int currentRowIndex = ProductGrid.Items.IndexOf(currRow); // Current row index
DataGridRow currentRow = ProductGrid.ItemContainerGenerator.ContainerFromIndex(currentRowIndex) as DataGridRow;
IntegerUpDown prop = ProductGrid.Columns[1].GetCellContent(currentRow) as IntegerUpDown; // Here I get a NULL for "prop"..!! :(
prop.Value = 1; // Automatically increase the value of IntegerUpDown from zero to one
}
}
}
To do this, I need to access the IntegerUpDown component of the selected row. Unfortunately, I have no idea of doing that.
I hope that some of you .NET geniuses may be able to help me in this matter.
Thanks very much in advance.
Reagrds,
Asela.
Okay, it's been some time since I answered any questions here, but yours is definitely worth some attention.
First of all, regarding this:
I'm a Java developer by profession
Forget java.
Most (if not all) of the (rather cumbersome and excessively verbose) patterns and paradigms you might be used to in java are of little or no use at all in C# and WPF.
This is because, in constrast to java, C# is a modern, professional-level language with many language features that provide ease of development and greatly reduce boilerplate.
In addition to that, WPF is an advanced, professional-level UI framework with tons of advanced features (most notably Data Binding and Data Templating) that allow you to create a high-level abstraction and completely separate your code and application logic from the UI components, achieving maximum flexibility without introducing nasty constructs or unnecessary coupling.
Such an abstraction is achieved by implementing a pattern called MVVM. This pattern is rather ubiquitous in most (if not all) modern UI technologies, both Web and Non-Web, except in the java world, which instead seems to believe (unsurprisingly) it's still 1990.
So, instead of trying to hammer concepts from legacy technologies and make them somehow fit into WPF, I suggest you take the time to understand, and embrace The WPF Mentality.
Now, I see several flaws in your code, both in terms of the code itself and in terms of the philosophy / approach you're using to write it.
First of all, the presence of things like Height="464" Margin="444,16,10,0" or the like in XAML indicate that you used the Visual Studio designer to build such UI. This is useful as a learning exercise, but it is highly discouraged for production code, for the reasons stated here.
I suggest you take the time to properly learn XAML and also look at this tutorial to understand how the WPF layout system works, and how to write resolution-independent, auto-adjustable WPF UIs rather than fixed-size, fixed-position layouts that don't properly adjust even when resizing the containing Window.
Again, the typical mistake developers make in WPF when coming from whatever other technologies is in how they approach things, rather than how they code them. Let's analyze your code:
var currRow = ProductGrid.CurrentItem; // Current row
InvoiceItemsDTO sel = (InvoiceItemsDTO)currRow; // Current row DTO OBJECT
if (sel != null)
{
//...
}
This code (aside from the fact that it could be shortened) is, at first glance, just fine. You're retrieving the underlying data object rather than trying to mess with the UI elements. This IS the correct approach in WPF. You need to operate on your data items and NOT the UI.
Let's rewrite it into a more C#-like way:
var row = ProductGrid.CurrentItem as InvoiceItemsDTO;
if (row != null)
{
//...
}
Note: The above code shows an example of how C# language level features (in this case, the as operator) help in reducing boilerplate (we now have 2 lines of code instead of 3) by allowing beautiful code that otherwise requires a bunch of horrible hacks in inferior technologies such as java.
Okay, So far so good, but then you slip away from this data-centric thinking into trying to manipulate the UI for some reason.
Think about it: you're trying to "update the Value property of the IntegerUpDown which corresponds to the currently selected row".
But, your XAML shows that the Value property of the IntegerUpDown is actually bound via Two-Way DataBinding to a property called productQuantity in the underlying data item.
So, basically, your code results in something like this:
get Data Item -> get UI item -> update UI item -> DataBinding updates Data Item.
See? you're creating a completely unnecessary indirection. Instead, simply operate on your data item rather than the UI, and let Two-Way databinding take care of the rest. That's the WPF mentality.
var row = ProductGrid.CurrentItem as InvoiceItemsDTO;
if (row != null)
{
row.productQuantity++;
}
See how much easier life is when you're dealing with modern technology?
But it doesn't even end there.
Your XAML also shows that the CheckBox you're dealing with has it's IsChecked property bound to a property called selected in the underlying Data item:
<CheckBox [...] IsChecked="{Binding Path=selected}"/>
This means that your InvoiceItemsDTO class has a public bool selected {...} property, right? So, instead of dealing with events at the UI level, (again), why don't you simply put the logic where it really belongs, and get rid of the UI dependencies, effectively making your code more testable, much cleaner, and simply beautiful?
public class InvoiceItemsDTO
{
private bool _selected;
public bool Selected
{
get { return _selected; }
set
{
_selected = value;
//This is where your code should be.
if (value)
ProductQuantity++;
else
ProductQuantity--;
}
}
}
As an aside, notice the use of proper casing. camelCasing is horrible, and thus reserved for private members only in C#. Not public ones.
See? simple, clean, testable, and beautiful, and it just works.
But, How does it work?
1 - When the Checkbox is clicked by the user, the IsChecked value is updated.
2 - WPF's DataBinding updates the value of the InvoiceItemsDTO.Selected property to true.
3 - Your code adds +1 to the ProductQuantity property.
4 - WPF's DataBinding reflects the ProductQuantity change in the UI, provided you have properly Implemented INotifyPropertyChange.
The same workflow occurs when un-checking the Checkbox, but with false value.
This removes the need for event handlers, casting, code behind, and other cumbersome approaches that require useless boilerplate and introduce unnecessary, undesired coupling.
Bottom line: C# Rocks. WPF Rocks. java is legacy.
Let me know if you need further help.

WPF Style Combobox with two columns in dropdown

I've looked around, found some things and now stuck on a combobox with two columns displayed in the drop-down area. I have a xaml themes available and the combobox "Style" is defined and works well throughout as expected, so that part is ok.
Now, I have a combobox that I need to have display two values, think of it as State Abbreviation and State Name for the drop-down, coming from a DataTable.DefaultView binding source for the items.
If I have
<my:cboStates TextSearch.TextPath="StateAbbrev">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" TextSearch.Text="{Binding Path=StateAbbrev}">
<TextBlock Text="{Binding Path=StateAbbrev}"/>
<TextBlock Text="{Binding Path=FullStateName}" Margin="10 0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</my:cboStates>
this works. Now, how/where I'm stuck... Now, I want this same functionality on say 5 different forms and all to have the same content displayed, and if ever changed (not this, but for other multi-column comboboxes), I don't want to have to keep putting this directly in the form's XAML file.
I was hoping to put into a Theme's Resource Dictionary file and just keep reusing that "style" over and over. Makes sense. However, when I do, and the binding is to the data table, the only results I get when trying to do as a Style is the dropdown shows values of
System.Data.DataRowView
System.Data.DataRowView
System.Data.DataRowView
System.Data.DataRowView
instead of the actual 2 columns.
Here is what I have in the "theme" resource dictionary.
<DataTemplate x:Key="myStateComboTemplate" >
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Path=StateAbbrev}"/>
<TextBlock Text="{Binding Path=FullStateName}"/>
</StackPanel>
</DataTemplate>
<Style x:Key="StyleMyStatesCombobox" TargetType="{x:Type ComboBox}"
BasedOn="{StaticResource MyOtherWorkingComboBoxStyle}" >
<Setter Property="TextSearch.TextPath" Value="{Binding Path=StateAbbrev}" />
<Setter Property="ItemTemplate" Value="{StaticResource myStateComboTemplate}" />
</Style>
So, If I have TWO instances my "cboStates" class created on the form, and set one to the explicit styling listed first, and the SECOND based on the "Style" setting, the second one fails by only showing the repeated System.Data.DataRowView entries, not the actual data content.
What am I missing.
So, to clarify what I'm looking for...
States... ex data
AL Alabama
AK Alaska
AZ Arizona
AR Arkansas
CA California
CO Colorado
CT Connecticut
DE Delaware
I want the combobox to be displaying the abbreviated
AL, AK, AZ, etc and narrower combobox. This will ALSO be the "SelectedValue" upon return.
The actual Dropdown would present the data as listed above showing BOTH the abbreviation AND the long description of the state.
Sample of desired combobox
FINALLY got it working... and for those attempting similar. Since I was trying to have a standard "class" instance that could be used throughout, but not wanting to explicitly hard-reference the XAML in each page, part of the styling had to be handled during the actual in-code class instance.
Since I don't exactly know how/when where .net framework builds out all its controls, style assigments, etc, I was getting frustrated that it would work if direct from xaml, but fail when in code. So, I ended up FORCING the item template AND TextSearch.TextPath values in the code. Here's a short snippet of the class
public class myStatesCombo : ComboBox
{
public myStatesCombo()
{
Loaded += myAfterLoaded;
}
protected static DataTable myTableOfStates;
public void myAfterLoaded()
{
if( myTableOfStates == null )
myTableOfStates = new DataTable();
CallProcedureToPopulateStates( myTableOfStates );
ItemsSource = myTableOfStates.DefaultView;
// AFTER the object is created, and all default styles attempted to be set,
// FORCE looking for the resource of the "DataTemplate" in the themes.xaml file
object tryFindObj = TryFindResource("myStateComboTemplate" );
if( tryFindObj is DataTemplate )
ItemTemplate = (DataTemplate)tryFindObj;
// NOW, the CRITICAL component missed in the source code
TextSearch.SetTextPath( this, "StateAbbrev" );
}
}
Now, a special note. In the routine I used to populate the DataTable, I pre-check if the table exists or not. First time in, I create the table. If I need to re-populate it, if I just keep doing a "new DataTable" each time, it blows away the data/item template bindings. To PREVENT that, I would then do
if( myTableOfStates.Rows.Count > 0 )
myTableOfStates.Rows.Clear();
THEN, I call my
Sqlexecute call to query from the database (DataAdapter) and Fill() the datatable.
So, now all appears to be populated correctly, bindings for display and textsearch complete and ready to go.

RibbonComboBox Text Not Retaining Edits

I have the following xaml in my ui:
<ribbon:RibbonGallery SelectedValue="{Binding Text}"
SelectedValuePath="Content"
SelectedItem="{Binding SelectedRemark, Mode=TwoWay}"
MaxColumnCount="1">
<ribbon:RibbonGalleryCategory ItemsSource="{Binding Remarks}" DisplayMemberPath="Text"/>
</ribbon:RibbonGallery>
Both SelectedRemark and Remarks are properties on my view model; and Remarks is an ObservableCollection<Remark>.
It will display properly, and I can edit the text in the combobox. However, as soon as the combobox loses focus, it reverts back to whatever the the original text was.
I'm new to WPF, and cannot figure out what am I doing wrong.
Beware of a bug that causes the selectionchanged event to fire after the mouse moves. See this bug report: https://connect.microsoft.com/VisualStudio/feedback/details/666352/
Those bindings are all related to selection. I'm not certain how the Ribbon operates but it doesn't appear that what you are trying to do will give you the results you are after.
In addition the ObservableCollection<Remark> is only relative from an Add/Remove stance when making use of binding. It will not propagate changes to the items within the collection. If you were wanting that functionality you will need to implement INotifyPropertyChanged on the Remark object, then raise a property changed notification as needed.

Categories

Resources