WPF: Editable ComboBox and StringFormat - combobox

(First of all, I simplified the sample for you to understand.)
I want a editable ComboBox that shows values in inches. The original (source) value is a value in pixels (DIPs), because this is the unit in which the application works internally. The user shouldn't see the value in pixels, but in inches (the measurement unit of the UI). I determined that I must use a converter to translate from pixels to inches and viceversa.
The ComboBox items should also be formatted with "{0} in".
It should appear like this
[ 4 in |v]
[ 1 in ]
[ 2 in ]
[ 3 in ]
[ 4 in ]
But, remember, internally it should work in Pixels! and Be Editable. This is the works part. I have been totally unable to achieve this.
I have this so far:
<ComboBox IsEditable="True"
ItemStringFormat="'{}{0} in}'"
Text="{Binding RelativeSource={RelativeSource Self}, Path=SelectedValue, Mode=TwoWay, Converter={StaticResource MyPixelToInchesConverter}, StringFormat='{}{0} in}'}">
<system:Double>1</system:Double>
<system:Double>2</system:Double>
<system:Double>3</system:Double>
</ComboBox>
The problem? When you select an item, it's not formatted. And when you write a number in the editable region of the ComboBox, it's not formatted, too.
So, what is the best way for a ComboBox to format items even when the user enters a non-existent value??
Thanks!

Probably a bit late, but I found that setting the Text property to use a binding can work, with a bit of a kludge. In my case I was working with a Currency value, but the same principles should apply
Text = {Binding Path=Indemnity, StringFormat=C0}
However, entering a text value not in the list still exhibits the problem that the StringFormat is not applied (for some strange reason known only to the MS folks who designed this).
To work around this, I added a LostFocus handler that simply writes an incorrect value, followed by writing the correct value again - at which stage the StringFormat is applied:
private void IndemnityCombo_LostFocus(object sender, RoutedEventArgs e)
{
var vm = this.DataContext as RatesViewModel; // A view model class
if (vm != null) {
decimal indemnity = vm.Indemnity;
vm.Indemnity = indemnity + 1M;
vm.Indemnity = indemnity;
}
}
It's not elegant, but it does the job for me. Hope it can be useful to other users.

Related

WPF Datagrid bound to ViewModel.DataTable. SelectedItem not binding to ViewModel.SelectedObject

TYFYT. I need some help understanding what's going on here and how binding to a datagrid works. I am coming from a knowledge base of working with WinForms so maybe my approach is all wrong?...
I have a WPF window, with a Datagrid.
In the ViewModel I have DataTable that I have populated with my Booking objects. The cols across the top correspond to a TimeSlot, and the row headers correspond to an instructor.
void DisplayBookings()
{
foreach (Booking booking in bookings)
{ //Add each Booking object to the DataTable
DtBookings.Rows[booking.GridRow][booking.SlotTimeId + 1] = booking;
}
}
In the window xaml I have the datagrid bound to the DtBookings.
<DataGrid x:Name="grdInstructors"
ItemsSource="{Binding DtBookings}" DisplayMemberPath="Id"
SelectedItem="{Binding SelectedCell}"
When I run this, it's working fine. I see the bookings in the correct cells in the grid.
Trying to do work against the grid is what is raising questions.
Q1. What is the grid cell holding? I have put the Booking objects in the ViewModel.DataTable and in the grid cell I see "myNamespace.Models.Booking". This is fine but when I try to access it I get the error "'Unable to cast object of type 'System.String' to type 'MyObject' (Booking)
So is the datagrid only holding a String that is the name of the object? I could accept that answer since I'm thinking the View should only be a visual representation of the objects.
If that is so, then how should SelectedItem=ViewModel.MySelectedItem work? (In this case it doesn't). Initially I thought it should have a copy of the object so that when I click on the cell it will tell the ViewModel what object I have chosen. This normally works when I am bound to an ObservableCollection, but in this case it is bound to a DataTable so I'm not getting the same result.
And on a final note I have noticed that DisplayMemberPath="Id" doesn't work in this case. (It's not really an issue since I usually override `ToString())
It would be great to have a definitive answer on this before I add logic to work around the problem and defeat the point of MVVM and Binding.
TYVM.

When does a ComboBox receive its Items if it is bound to ObservableCollection?

I am attempting a save/load mechanism for re-use in a business application. I have the groundwork laid to read/write ObservableCollection<> to/from xml, using attributes to describe my class properties. That part is working. I can save an ObservableCollection to XML, then load the XML back into an ObservableCollection the next time I run the program.
Here's my problem. I have a ComboBox whose ItemsSource.DataContext = ObservableCollection<Flag>;
When I run the program, it accepts the binding just fine, but the ComboBox itself does not populate itself until later. I want to set the SelectedItem to be the first item in the ObservableCollection<Flag> that I have loaded from XML. Nothing happens though, because as the program is executing it's startup methods, the Items.Count remains 0. I'm guessing the ComboBox doesn't populate itself until it gets focus. How do I work around this? Can I force the ComboBox to populate itself? I've tried cb_ARDAR_ARFlag.Items.Refresh();
XAML:
<ComboBox Name="cb_ARDAR_ARFlag"
ItemsSource="{Binding}"
SelectionChanged="cb_ARDAR_ARFlag_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Flag_Desc}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Relevant Code:
public MainWindow()
{
InitializeComponent();
setDataBinding();
loadSavedData();
}
private void setDataBinding()
{
//Returns ObservableCollection<Flag>
cb_ARDAR_ARFlag.DataContext = Flag.getOCAvailableFlags();
}
private void loadSavedData()
{
//When it gets here the ItemCount is 0 so nothing happens.
//Refresh didn't help
cb_ARDAR_ARFlag.Items.Refresh();
Flag f = Enforcement_Save.loadOCARFlag().First();
cb_ARDAR_ARFlag.SelectedItem = f;
}
At this point I'm still not sure the code at the end will successfully identify the correct 'flag' item to be selected, or if I'll end up using Linq. Which, by the way, leads me to another question. Can you Linq to ComboBox.Items somehow?
I have recreated your issue, and your are correct, the items count is = 0 in the loadSavedData method. The combobox doesn't seem to be populated until after the constructor has fully executed.
In the meantime I found you can use the ItemsSource property to load the combobox at the time you want it loaded:
cb_ARDAR_ARFlag.ItemsSource = Flag.getOCAvailableFlags();

WPF DataGrid - how to stop user proceeding if there are invalid cells? (MVVM)

I'm implementing edit functionality in my DataGrid. The CellEditingTemplate of a typical cell looks something like this:-
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Grid.Column="0"
Text="{Binding Concentration, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=LostFocus}"
Validation.ErrorTemplate="{StaticResource errorTemplate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
In this example there is a TextBox bound to a property called "Concentration", which is a property of type double, with validation to ensure that it falls within a given range. The models that are being bound to the grid implement IDataErrorInfo by the way.
The problem is, although the error template highlights the cell when an invalid value is entered, there is nothing to stop me from moving focus away from that cell (or row). What's the preferred approach for handling validation with data grid editing? I wondered if I could prevent the user from leaving the cell until a valid value was entered, but that could be dangerous - they wouldn't be able to do anything (even close the app) until a valid value had been entered.
Another option might be to prevent the user from proceeding if the datagrid contains any invalid rows. But what's the best way to test this? IDataErrorInfo doesn't provide an "IsValid" property that I can examine on each of the rows' models.
Lastly I could just revert the model's property back to its previous "good" value (or a default value) if the user enters something invalid.
Any suggestions? I'm using MVVM by the way.
I use this to see if IDataerrorInfo has any errors for the object, a small snippet of the implementation:
protected Dictionary<string, string> _propertyErrors = new Dictionary<string, string>();
public bool HasErrors {
get { return (_propertyErrors.Count) > 0; }
}
Then I can handle the logic for what to do after evaluating this property. Do you want to prevent navigation, closing app, etc. Then you need to evaluate for errors from that code and then cancel that action.
I've used this method in the past to determine if a datagrid has errors:
private bool HasError(DataGrid dg,)
{
bool errors = (from c in
(from object i in dg.ItemsSource
select dg.ItemContainerGenerator.ContainerFromItem(i))
where c != null
select Validation.GetHasError(c)
).FirstOrDefault(x => x);
return errors;
}
Then it's only a matter of preventing the next action if the method returns true.

Looping through (and removing) items of a bound ComboBox

I am new to WPF - and painfully aware of it. I have unsuccessfully searched for answers to this particular problem and am now seeking advice from my more knowledgeable peers!
The scenario
The application on which I am working allows users to either enter new records into a database, or amend existing ones.
I have a form containing a bound ComboBox. It is populated from the database, which is accessed by a WPF service that exposes a DTO.
From the UI perspective, the form has two modes:
1. enter new record
2. amend existing record
The ComboBox in question appears in both cases, but the requirement is to have fewer options visible when the form is in 'amend' mode.
What I am trying to do is loop through the ComboBox items when the form is in 'amend' mode and remove/hide the options that should not appear.
The XAML
<ComboBox x:Name="RecordType" Grid.Column="1" Grid.Row="1" Width="150" HorizontalAlignment="Left" SelectedValue="{Binding Path=RecordTypeID,TargetNullValue=0}"/>
The code behind - and my (feeble!) attempts so far
foreach (ComboBoxItem item in this.RecordType.Items)
{
if (IsApplicable(item.Content.ToString()) == false)
{
item.Visibility = Visibility.Hidden;
}
}
(NOTE: IsApplicable() is a simple method that compares the string it receives to a list of the options that are allowed to appear when the form is in 'amend' mode.)
The problem
As I'm sure many of you will already know ... cannot cast object of type DTO to type System.Windows.Controls.ComboBoxItem
The question(s)
Can I get at the string values in this, or a similar way? If so, how, please?
Correct way to do this would be to apply Filter on Collection View
See Automatically Filtering a ComboBox in WPF
ICollectionView view = CollectionViewSource.GetDefaultView(comboBox.ItemsSource);
view.Filter = IsApplicable
view.Refresh(); // <-- call this whenever you change the view model
It would likely be easier for you if you bound your combobox to an ObservableCollection and then removed the items from the collection when needed.
Here is an example: http://www.tanguay.info/web/index.php?pg=codeExamples&id=304

Shortest way to get display value from wpf combobox

To get the currently displayed value from a WPF combobox, I am getting GetSelectedItem (which gives me a dataRowView since my itemSource is a DataView) and then getting the appropriate column.
I was hoping there could be straightforward way to get the Display Value like how we have the SelectedValue property.
Is anyone aware of a better approach?
You use the ADO.Net class DataTable, right?
You can set a displayed value quite straightforward:
<ComboBox x:Name="myComboBox" ItemsSource="{Binding}" DisplayMemberPath="SomeColumn"
SelectedValuePath="SomeColumn"/>
In this example the combobox displays the value of the column SomeColumn. Put a correct column name instead of this dummy one.
And in code-behind:
myComboBox.DataContext = myDataSet.Customers; //any table
var selectedValue = myComboBox.SelectedValue; //The displayed value (SomeColumn)
var fullRow = myComboBox.SelectedITem; //dataRowView, I think

Resources