Select the "old value" in a listbox in a edit page - silverlight

In my app I have a Save / Edit page.
The current flow is the following: the user has a main page, with a list of elements. He can click on the "add" button, it goes to an "Add" page, in which he can enter information and store it. Once he does it, the information is saved and shown in the list.
If he clicks in the list, he moves to an "Edit" page, in which he can change the information.
In reality, the Add and the Edit page are the same, the second has the fields populated while the first hasn't.
I have 3 listboxes in this page, one for severities, one for categories and one for reporter. This information is selected in the listbox before saving, and in the edit phase, it should be selected automatically, so the user knows the "old" value.
To select the values automatically, I tried two approaches:
1-In my xaml:
<ListBox Height="103" Name="lbSeverities" Width="439" HorizontalAlignment="Left" Margin="20,0,0,0" SelectionMode="Single" ItemsSource="{Binding Severities}" DisplayMemberPath="Name" SelectedItem="{Binding Task.Severity}"/>
And I also override the Equals method of the Severity Class to a reasonable implementation.
2- In my xaml
<ListBox Height="103" Name="lbSeverities" Width="439" HorizontalAlignment="Left" Margin="20,0,0,0" SelectionMode="Single" ItemsSource="{Binding Severities}" DisplayMemberPath="Name" SelectedIndex="{Binding Task.Severity, Converter={StaticResource SeverityToIndexConverter}}"/>
And I created the SeverityToIndexConverter with this code:
public class SeverityToIndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value is Severity)
{
Severity currentSeverity = (Severity)value;
for (int i = 0; i < (App.Current as App).MainViewModel.Severities.Count; i++)
{
Severity sev = (App.Current as App).MainViewModel.Severities[i];
if (currentSeverity.ID == sev.ID)
return i;
}
}
return -1;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
Both of them presented the same results: sometimes the values are automatically selected, but sometimes not. It is very unstable when it is selected.
I imagined about some exception, tried to catch it, but I do not get anything.
I also tried to debug, I noticed that in case 1, the equals method is called in parallel for all members of the collection, so I tried the second approach. Debugging didn't lead me to any answer.
Has anyone faced a similar situation?
What can I do to make the Listbox value to be selected when the user enters in the "Edit" Page?
Thanks,
Oscar

I do the same thing. My xaml looks like your first example.
Here's what I'd do:
Create a new property called Severity. In the getter return Task.Severity. Bind the listbox SelectedItem to the new property.
It may also be a timing thing so you may have to call NotifyPropertyChanged on the new property once the listbox has loaded its list.

Related

WPF MVVM enable button in child DataGrid with Command and CommandParameter

I have a WPF application wherein a Window has a master/child relationship. When a user selects a Buyer from a ListView, a DataGrid is bound with related Items they have purchased. In the GridView, there is a button on the left where a Payment for this individual item can be made. However, I want to disable/enable the button based on the state of the Item, whether or not an existing Payment is associated with it. Using Galasoft MVVM Light, RelayCommand throughout the application. I have the Pay button working, but ONLY when clicked (row selected). This works fine when bound to a command in this way:
<DataGrid ..
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
IsSynchronizedWithCurrentItem="True"
.. >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button ...
Command="{Binding Path=DataContext.PayItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
CommandParameter="{Binding SelectedItem, Mode=OneWay, ElementName=itemsPurchasedGrid}"
>Pay</Button>
...
// in ctor
PayItemCommand = new RelayCommand<Item>(PayItem, IsItemUnPaid);
// in body
private void PayItem(Item item)
{
PaymentMode = PaymentMode.Item;
Messenger.Default.Send<BuyerViewModel>(this, "LaunchPayWindow");
}
// CanExecute impl
private bool IsItemUnPaid(Item item)
{
// code to determine if item is already paid
}
So, when I click the button, it correctly passes the SelectedItem to the RelayCommand delegate. However, the CanExecute implementation: IsItemUnPaid(Item item) never gets the item, because it is not (yet) selected. I tried: IsSynchronizedWithCurrentItem="True", which always passes the first Item, but not any others. Is there a way to get a handle to the Item while it is binding to the DataGrid to disable/enable it? I'm sure there are ugly ways in the codebehind during binding, but is there an MVVM way to check the current item and toggle that button? Many thanks!
P.S. I wrote a simple converter which works, but is that really the best way?
public class PayItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is Item item))
return true;
// if you find any payments for this item...
var payments = (item.Payments != null && item.Payments.Any(p => p.ItemId.HasValue && p.ItemId.Value == item.Id))
? item.Payments.Where(p => p.ItemId.HasValue && p.ItemId.Value == item.Id).ToArray()
: null;
// if you find payments for this item, are the sum of the payments less than the total of the item TotalAmount, it is said to be unpaid
return (payments != null && payments.Length > 0)
? payments.Sum(p => p.Amount) < item.TotalAmount
: true;
}
No, a converter is not the best way of implementing this. The whole point of MVVM is to maintain good separation-of-concerns between your view and your view logic. Converters most definitely reside in the view layer; as soon as you find yourself adding application-type logic to a converter it's usually a sign that your view model is not coercing your data into a format that can be readily consumed by the view.
The problem here is that your Items property seems to be model data, presumably from a database or something. What it needs to be is a collection of view models, one per item, which exposes the properties of the item (or even the whole item itself) and also contains extra fields for the logic needed by your view i.e.:
public bool PayButtonEnabled {get; private set;}

DataGrid refresh issue

I have a Silverlight 4 DataGrid which has its ItemsSource bound to an ObservableCollection. When i modify an element of my ObservableCollection the modified element is correctly displayed inside my grid except the element of one column. This columns differs from the others in the way it is a TemplateColumn and it's using a ValueConverter.
The Template for the column consists of a simple stackPanel that includes a Path control and a Label. And the Label is bound to some Source object with the help of a simple ValueConverter.
The problem now is when i modify some element that belongs to the ObservableCollection all columns of the grid are displayed correctly except the one described above. It simply stays unchanged - but when i use the mousecursor to select the DataGridCell and click it a second time, the desired refresh suddenly happens.
So I guess it's something simple what i am missing here, but I can't find it ...
Thanks in advance ..
EDIT:
In the meanwhile I was able to further locate the problem: It seems that after I modify an element of my ObservableCollection the corresponding ValueConverter that belongs to the label that is in my grid that is bound to the source is simply not called. When i click inside the cell the ValueConverter is getting called as it should. BUT it won't automatically - So how do I achieve that ? please help :)
EDIT:
The binding:
<sdk:Label Content="{Binding Route.Legs, Converter={StaticResource IncomingTableRouteTripConverter}}" Margin="9,0,0,0" Style="{StaticResource TripLabelTemplate}" FontFamily="Arial" FontSize="10.667" Padding="0" Height="10" FontWeight="Bold" />
This is the code of my ValueConverter:
(But I don't think that the code of the converter has anything to do with my problem I only posted it here for completeness)
public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
string trip = "";
if (value != null) {
List<Leg> legs = (List<Leg>)value;
if (legs.Count >= 1) {
for (int i = 0; i <= legs.Count - 1; i++) {
trip += ((Leg)legs[i]).Start.ICAO + " - " + ((Leg)legs[i]).Stop.ICAO + " - ";
}
trip = trip.Substring(0, trip.Length - 2);
}
}
return trip;
}
For all nodes in the Path notifications need to be in place, so both the class owning Route and the class owning Legs need to implement INPC.
Further if you add items to the Legs list naturally nothing will be updated, in fact even if the Legs property were of type ObservableCollection<...> that would not matter as the binding engine only cares about INPC.
So if you want the binding to update if the collection changes you need to fire property changed for the Legs property every time it somehow is modified (including a complete replacement of the reference).
IF you use like
Content="{Binding Path=Parameter Converter={StaticResource SomeConverter}}"
then your problem might be solved...

Databinding with kind of join over two lists

I am familiar with the basics of databinding in wpf. However I now have a problem which I wonder how to solve.
Imagine following use case:
I have a global ObservableCollection called "AItems" of Type A.
I have some Objects of Type B and each has a ObservableCollection "BItems" of type A.
The BItems Collections can contain Objects of the global AItems Collection.
I want to visualize this by a ListView.
Each line should contain an A-Object and a checkbox.
I want the ListView to show all elements of the AItems-Collection. Items which are assigned to the B-Object should be marked with a checked checkbox. All other checkboxes should be unchecked.
My questions are now:
How should I set the datacontext?
How can I make that checking a checkbox inserts its item to the BItems-Collection and unchecking removes it?
I hope anyone can understand my problem.
Thanks for replies.
I'm not clear on the latter part of your question. Partly it's because your naming convention is confusing; I'd expect a collection named BItems to contain objects of type B, not A.
So I'm going to change your nomenclature a bit so that I don't get confused. Instead of A, I'll call the first class User, and instead of B, I'll call the second class Group. A Group contains a collection of User objects, named Users. The global collections look like this:
List<User> Users;
List<Group> Groups;
It's easy to determine if a given User u is in any group:
return Groups.Where(g => g.Users.Contains(u)).Any();
Easy, but computationally expensive if you have many groups and they contain many users. We'll get back to that in a second.
Right away, I see that one of your questions has got a problem:
How can I make that checking a checkbox inserts its item to the BItems-Collection and unchecking removes it?
What should happen if I check an unchecked user? Which group (or groups, since more than one group can contain a user) should it be added to?
Since you say that you want checked items to be "assigned to the B-Object", I'm going to assume that the UI is only looking at one group at a time - we'll call it the SelectedGroup. This is good, because g.Users.Contains(u) is much less expensive than the query I showed above.
If this is so, what you need to do is wrap your User in a class that exposes an IsChecked property. I'd call this class UserViewModel, since that's what it is. The class needs three properties (at a minimum):
public User User { get; set; }
public Group SelectedGroup { get; set; }
public bool IsChecked
{
get { return SelectedGroup.Users.Contains(this.User); }
set
{
if (value != IsChecked)
{
if (IsChecked)
{
SelectedGroup.Users.Remove(this.User);
}
else
{
SelectedGroup.Users.Add(this.User);
}
}
}
}
Your ListView is bound to an ObservableCollection<UserViewModel> named, say, UserViewModels. Whenever SelectedGroup is set, you need to rebuild this collection:
UserViewModels = new ObservableCollection<UserViewModel>(
Users.Select(u => new UserViewModel { User=u, SelectedGroup=SelectedGroup }));
You could avoid rebuilding the collection by implementing INotifyPropertyChanged in the UserViewModel class, and having it raise PropertyChanged for the IsChecked property whenever SelectedGroup changes.
Also, it would probably be responsible to include null-reference checking in the IsChecked property, so that the program doesn't throw an exception if SelectedGroup or SelectedGroup.Users is null.
You can bind a list box to Aitems and using a converter to set the isChecked property
<ListBox ItemsSource="{Binding AItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" IsChecked="{Binding Mode=OneTime, Converter={StaticResource BItemCheckConverter}}"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public class BItemCheckConverter : IValueConverter
{
public List<Aitems> BItems { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (BItems.Contains((value as Aitems)) return true;
else return false
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I don't know how's your object model, so take the above code as is.
HTH

Preventing the User to select a tab WPF Tab Item

I had to prevent the user from selecting a tabitem in a WPF TabControl,
1)unless and untill the user checks a check box in one condition the user should be shown a message box and if he checks the check box he can navigate to any other tab
2)Checking a particular condition the user shouldnt be able to get into a particular tab on selecting it,and I dont have an option of making the tab item collapse.and it should pop up a message box and get back to the same prv tab item selected
I have seen Smith Josh's sample code as below and this is what i exactly wanted for the 1st scenerio
http://joshsmithonwpf.wordpress.com/2009/09/04/how-to-prevent-a-tabitem-from-being-selected/
But I need something that works in MVVM, where my application has a strict "No CodeBehind"
You could inherit the TabControl (or add an attached property) which controls if navigation to another tab item is allowed; however, let me just stress that 'no codebehind' is kinda silly - there are plenty of times when code-behind can be used for view-only purposes, and that's ok.
Back to the problem... what you'd do using my suggestion is hide the code-behind (checking if the action is allowed) inside a control, so that the actual view (the page/window etc) doesn't contain it. If you declare the new property as a DependencyProperty you get all the binding facilities etc.
EDIT: I tested my other code and it didn't work. Was just an idea anyways. Here's a method that does work (although I agree with Alex that code behind in MVVM is fine when adjusting the View).
In this case I created a converter which takes two boolean values: If the tab is selected and if we can change tabs. If both of these are set to false, we return false to disable the tab. If either is set to true, we leave the tab enabled.
Here's the code. I have a property in my VM called CanChangeTabs and an instance of MyConverter in Window.Resources called Converter.
XAML inTabItem:
<TabItem.IsEnabled>
<MultiBinding Converter="{StaticResource Converter}">
<Binding RelativeSource="{RelativeSource Self}" Path="IsSelected" />
<Binding Path="CanChangeTabs" />
</MultiBinding>
</TabItem.IsEnabled>
Converter:
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
foreach (object value in values)
{
if ((bool)value)
{
return true;
}
}
return false;
}
public object[] ConvertBack(object values, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Why can't I select a null value in a ComboBox?

In WPF, it seems to be impossible to select (with the mouse) a "null" value from a ComboBox. Edit To clarify, this is .NET 3.5 SP1.
Here's some code to show what I mean. First, the C# declarations:
public class Foo
{
public Bar Bar { get; set; }
}
public class Bar
{
public string Name { get; set; }
}
Next, my Window1 XAML:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ComboBox x:Name="bars"
DisplayMemberPath="Name"
Height="21"
SelectedItem="{Binding Bar}"
/>
</StackPanel>
</Window>
And lastly, my Window1 class:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
bars.ItemsSource = new ObservableCollection<Bar>
{
null,
new Bar { Name = "Hello" },
new Bar { Name = "World" }
};
this.DataContext = new Foo();
}
}
With me? I have a ComboBox whose items are bound to a list of Bar instances, one of which is null. I have bound the window to an instance of Foo, and the ComboBox is displaying the value of its Bar property.
When I run this app, the ComboBox starts with an empty display because Foo.Bar is null by default. That's fine. If I use the mouse to drop the ComboBox down and select the "Hello" item, that works too. But then if I try to re-select the empty item at the top of the list, the ComboBox closes and returns to its previous value of "Hello"!
Selecting the null value with the arrow keys works as expected, and setting it programatically works too. It's only selecting with a mouse that doesn't work.
I know an easy workaround is to have an instance of Bar that represents null and run it through an IValueConverter, but can someone explain why selecting null with the mouse doesn't work in WPF's ComboBox?
Well I recently ran into the same problem with null value for ComboBox.
I've solved it by using two converters:
For ItemsSource property: it replaces null values in the collection by any value passed inside converter's parameter:
class EnumerableNullReplaceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = (IEnumerable)value;
return
collection
.Cast<object>()
.Select(x => x ?? parameter)
.ToArray();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
For SelectedValue property: this one does the same but for the single value and in two ways:
class NullReplaceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value ?? parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.Equals(parameter) ? null : value;
}
}
Example of use:
<ComboBox
ItemsSource="{Binding MyValues, Converter={StaticResource EnumerableNullReplaceConverter}, ConverterParameter='(Empty)'}"
SelectedValue="{Binding SelectedMyValue, Converter={StaticResource NullReplaceConverter}, ConverterParameter='(Empty)'}"
/>
Result:
Note:
If you bind to ObservableCollection then you will lose change notifications. Also you don't want to have more than one null value in the collection.
The null "item" is not being selected by the keyboard at all - rather the previous item is being unselected and no subsequent item is (able to be) selected. This is why, after "selecting" the null item with the keyboard, you are thereafter unable to re-select the previously selected item ("Hello") - except via the mouse!
In short, you can neither select nor deselect a null item in a ComboBox. When you think you are doing so, you are rather deselecting or selecting the previous or a new item.
This can perhaps best be seen by adding a background to the items in the ComboBox. You will notice the colored background in the ComboBox when you select "Hello", but when you deselect it via the keyboard, the background color disappears. We know this is not the null item, because the null item actually has the background color when we drop the list down via the mouse!
The following XAML, modified from that in the original question, will put a LightBlue background behind the items so you can see this behavior.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ComboBox x:Name="bars" Height="21" SelectedItem="{Binding Bar}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Background="LightBlue" Width="200" Height="20">
<TextBlock Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>
If you want further validation, you can handle the SelectionChanged event on the ComboBox and see that "selecting the null item" actually gives an empty array of AddedItems in its SelectionChangedEventArgs, and "deselecting the null item by selecting 'Hello' with the mouse" gives an empty array of RemovedItems.
I got a new solution for this question. "USING Mahapps"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
<ComboBox x:Name="bars" **controls:TextBoxHelper.ClearTextButton="True"**
DisplayMemberPath="Name"
Height="21"
SelectedItem="{Binding Bar}"/>
You can use the close button to clear the content.
Thanks.
I know this answer isn't what you asked for (an explanation of why it doesn't work with the mouse), but I think the premise is flawed:
From my perspective as a programmer and user (not .NET), selecting a null value is a bad thing. "null" is supposed to be the absence of a value, not something you select.
If you need the ability explicitly not to select something, I would suggest either the work-around you mentioned ("-", "n.a." or "none" as a value), or better
wrap the combobox with a checkbox that can be unchecked to disable the combobox. This strikes me as the cleanest design both from a user's perspective and programmatically.
I spent one day to find a solution about this problem of selecting a null value in combobox and finally, yeah finally I found a solution in an article written at this url:
http://remyblok.tweakblogs.net/blog/7237/wpf-combo-box-with-empty-item-using-net-4-dynamic-objects.html
public class ComboBoxEmptyItemConverter : IValueConverter
{
/// <summary>
/// this object is the empty item in the combobox. A dynamic object that
/// returns null for all property request.
/// </summary>
private class EmptyItem : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
// just set the result to null and return true
result = null;
return true;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// assume that the value at least inherits from IEnumerable
// otherwise we cannot use it.
IEnumerable container = value as IEnumerable;
if (container != null)
{
// everything inherits from object, so we can safely create a generic IEnumerable
IEnumerable<object> genericContainer = container.OfType<object>();
// create an array with a single EmptyItem object that serves to show en empty line
IEnumerable<object> emptyItem = new object[] { new EmptyItem() };
// use Linq to concatenate the two enumerable
return emptyItem.Concat(genericContainer);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<ComboBox ItemsSource="{Binding TestObjectCollection, Converter={StaticResource ComboBoxEmptyItemConverter}}"
SelectedValue="{Binding SelectedID}"
SelectedValuePath="ID"
DisplayMemberPath="Name" />
this might not address your answer completely, but hopefully its a hit in the right direction:
Have you installed SP1?
From Scott Gu's Blog:
NET 3.5 SP1 includes several data binding and editing improvements to
WPF. These include:
StringFormat support within {{ Binding }} expressions to enable easy
formatting of bound values
New alternating rows support within controls derived
from ItemsControl, which makes
it easier to set alternating properties on rows (for example: alternating background colors)
Better handling and conversion support for null values
in editable controls Item-level
validation that applies validation rules to an entire bound item
MultiSelector support to handle multi-selection and bulk
editing scenarios
IEditableCollectionView support to interface data controls
to data sources and enable editing/adding/removing items in a transactional way
Performance improvements when binding to IEnumerable data
sources
Sorry if I wasted your time and this was not even close..but I think the problem is inherited from:
constraints of the strongly typed dataset
NullValueDataSet Explained here
But now the SP1 for .Net 3.5 should have addressed this issue..
I had the same kind of problem we did some work around like adding a value property to the collection item like this :
public class Bar
{
public string Name { get; set; }
public Bar Value
{
get { return String.IsNullOrEmpty(Name) ? null : this; } // you can define here your criteria for being null
}
}
Then while adding items instead of null I use the same object :
comboBox1.ItemsSource= new ObservableCollection<Bar>
{
new Bar(),
new Bar { Name = "Hello" },
new Bar { Name = "World" }
};
And instead of selecteditem I bind it to selectedvalue :
<ComboBox Height="23" Margin="25,40,133,0" DisplayMemberPath="Name"
SelectedValuePath="Value"
SelectedValue="{Binding Bar}"
Name="comboBox1" VerticalAlignment="Top" />
I know It is not a complete solution, just one workaround I use
Try Binding.FallbackValue
From 6 Things I Bet You Didn't Know About Data Binding in WPF
ComboBox needs a DataTemplate to display the item no matter how simple it is.
DataTemplate works like this: get a value from instance.[path], e.g.
bar1.Car.Color
So it cannot get a value from
null.Car.Color
It will throw a null reference exception. So, the null instance will not be displayed. But the the Color - if it is a reference type - is allowed to be null because there will be no exception in this case.
Just a guess, but I think it sounds reasonable.
Assume combobox is using "ListCollectionView" (lcv as its instance) as its item collection, which it should be.
If you are a programmer, what you gonna do?
I will respons to both Keyboard and Mouse.
Once I get Keyboard input, I use
lcv.MoveCurrentToNext();
or
lcv.MoveCurrentToPrevious();
So, sure keyboard works well.
Then I am working on respons Mouse inputs. And it comes the problem.
I want to listen 'MouseClick' event of my item. But probably, my Item doesn't generated, it is just a placeholder. So when user click on this placeholder, I get nothing.
If I get the event successfully, what's next. I will invoke
lcv.MoveCurrentTo(selectedItem);
the "selectedItem" which would be null is not an acceptable parameter here I think.
Anyway, it's just guessing. I don't have time to debug into it though I am able to. I have a bunch of defects to fix. Good Luck. :)

Resources