I have a problem building a simple crud form in WP7. I have spent a lot of time to display an Enum into a listpicker an now I see InvalidCastException when trying to bind to the (IsolatedStorage) object.
public class Bath {
public string Colour { get; set; }
public WaterType WaterType { get; set; }
}
public enum WaterType {
Hot,
Cold
}
The enum is bound to a ListPicker, but as there is not enum.GetValues() in WP7 this is not a simple task.
I have a simple type class...
public class TypeList
{
public string Name { get; set; }
}
And in my viewmodel, I have ObservableCollection and mock the values from the enum...
private ObservableCollection<TypeList> _WaterTypeList;
public ObservableCollection<TypeList> WaterTypeList
{
get { return _WaterTypeList; }
set
{
_WaterTypeList= value;
NotifyPropertyChanged("WaterTypeList");
}
}
public void LoadCollectionsFromDatabase()
{
ObservableCollection<TypeList> wTypeList = new ObservableCollection<WaterTypeList>();
wTypeList.Add(new TypeList{ Name = WaterType.Hot.ToString() });
wTypeList.Add(new TypeList{ Name = WaterType.Income.ToString() });
WaterTypeList = new ObservableCollection<TypeList>(wTypeList);
}
Finally, my xaml contains the listbox...
<toolkit:ListPicker
x:Name="BathTypeListPicker"
ItemsSource="{Binding WaterTypeList}"
DisplayMemberPath="Name">
</toolkit:ListPicker>
Im not sure if the above is best practise and indeed if the above is part of the problem but the above does give me a populated ListPicker.
Finally, when the form is submitted the cast causes a InvalidCastException.
private void SaveAppBarButton_Click(object sender, EventArgs e)
{
var xyz = WaterTypeList.SelectedItem; // type AppName.Model.typeList
Bath b = new Bath
{
Colour = ColourTextBox.Text ?? "Black",
WaterType = (WaterType)WaterTypeListPicker.SelectedItem
};
App.ViewModel.EditBath(b);
NavigationService.Navigate(new Uri("/Somewhere.xaml", UriKind.Relative));
}
}
Has anyone faced a simlar problem and can offer advice. I see that my opions are to concentrate on casting something meaningful from the ListPicker or should I rethink the way that the ListPicker is populated?
As far as I can see, WaterTypeList is an ObservableCollection that is a Type of and an observable collection doesn't have a SelectedItem property.
Your Bath class has a WaterType that accepts WaterType property and you are trying to cast a WaterTypeListPicker.SelectedItem to it.. so I'm assuming your WatertypeListPicker is your ListBox?
If it is, then you are doing it wrong because your ListBox's itemssource is bound to a class and you are trying to add a to your WaterType Property.
What I would do is say,
Bath b = new Bath
{
Colour = ColourTextBox.Text ?? "Black",
WaterType = WaterTypeListPicker.SelectedItem
};
Either change my property of Bath's WaterType to a TypeList so the above code would work. But I won't recommend doing another class to wrap the enum just to show it to the listbox.
What I would do is create an EnumHelper
public static class EnumExtensions
{
public static T[] GetEnumValues<T>()
{
var type = typeof(T);
if (!type.IsEnum)
throw new ArgumentException("Type '" + type.Name + "' is not an enum");
return (
from field in type.GetFields(BindingFlags.Public | BindingFlags.Static)
where field.IsLiteral
select (T)field.GetValue(null)
).ToArray();
}
public static string[] GetEnumStrings<T>()
{
var type = typeof(T);
if (!type.IsEnum)
throw new ArgumentException("Type '" + type.Name + "' is not an enum");
return (
from field in type.GetFields(BindingFlags.Public | BindingFlags.Static)
where field.IsLiteral
select field.Name
).ToArray();
}
}
And Bind it to a Collection
My ViewModel
public IEnumerable<string> Priority
{
get { return EnumExtensions.GetEnumValues<Priority>().Select(priority => priority.ToString()); }
public string SelectedPriority
{
get { return Model.Priority; }
set { Model.Priority = value; }
}
Like that.
My XAML.
<telerikInput:RadListPicker SelectedItem="{Binding SelectedPriority, Mode=TwoWay}" ItemsSource="{Binding Priority}" Grid.Column="1" Grid.Row="4"/>
The WaterTypeListPicker.SelectedItem is an object of type TypeList so it can't be cast to an object of type WaterType.
In order to convert back to a WaterType, you could replace your cast:
WaterType = (WaterType)WaterTypeListPicker.SelectedItem
with:
WaterType = (WaterType)Enum.Parse(
typeof(WaterType),
((TypeList)WaterTypeListPicker.SelectedItem).Name,
false)
Related
I have a ComboBox being populated where each object in ComboBox.Items is a List of objects. Currently the ComboBox displays "(Collection)" for each Item.
Is it possible to have the ComboBox display a member of the first object in the List that comprises an Item of the ComboBox?
I am currently populating the ComboBox items by the following:
foreach(List<SorterIdentifier> sorterGroup in m_AvailableSorterGroups)
{
// There are conditions that may result in the sorterGroup not being added
comboBoxSorterSelect.Items.Add(sorterGroup);
}
//comboBoxSorterSelect.DataSource = m_AvailableSorterGroups; // Not desired due to the comment above.
//comboBoxSorterSelect.DisplayMember = "Count"; //Displays the Count of each list.
The value that I would like to have displayed in the ComboBox can be referenced with:
((List<SorterIdentifier>)comboBoxSorterSelect.Items[0])[0].ToString();
((List<SorterIdentifier>)comboBoxSorterSelect.Items[0])[0].DisplayName; // public member
You can create an object wrapper and override the ToString() method:
public class ComboBoxSorterIdentifierItem
{
public List<SorterIdentifier> Items { get; }
public override string ToString()
{
if ( Items == null || Items.Count == 0) return "";
return Items[0].ToString();
}
public BookItem(List<SorterIdentifier> items)
{
Items = items;
}
}
You should override the SorterIdentifier.ToString() too to return what you want like DisplayName.
Now you can add items in the combobox like this:
foreach(var sorterGroup in m_AvailableSorterGroups)
{
item = new ComboBoxSorterIdentifierItem(sorterGroup);
comboBoxSorterSelect.Items.Add(item);
}
And to use the selected item for example, you can write:
... ((ComboBoxSorterIdentifierItem)comboBoxSorterSelect.SelectedItem).Item ...
I could imagine a few ways to do this... you could create a class that extends List<T>, so you have an opportunity to define the value you'd like to display.
public class SortedIdentifier
{
public string Name { get; set; }
}
public class SortedIdentifiers : List<SortedIdentifier>
{
public string SortedIdentifierDisplayValue
{
get { return this.FirstOrDefault()?.Name ?? "No Items"; }
}
}
Then use the new class like this:
comboBox1.DisplayMember = "SortedIdentifierDisplayValue";
var list = new SortedIdentifiers { new SortedIdentifier { Name = "John" } };
comboBox1.Items.Add(list);
I have searched Google for a simple solution to this but no luck. I have a standard WPF combo box which I would simply like to be able to filter the list displayed according to the first 2 or 3 letters a users types when the combo box has focus. I tried some coding including some lamba expressions but the error "System.NotSupportedException" keeps getting thrown on the line where "combobox.Items.Filter" is specified. I'm not using MVVM and would just like this simple functionality available for the user. Please help! P.S. IsEditable, IsTextSearchEnabled and StaysOpenOnEdit properties are set to true but the desired functionality is not yet achieved.
I have developed a sample application. I have used string as record item, you can do it using your own entity. Backspace also works properly.
public class FilterViewModel
{
public IEnumerable<string> DataSource { get; set; }
public FilterViewModel()
{
DataSource = new[] { "india", "usa", "uk", "indonesia" };
}
}
public partial class WinFilter : Window
{
public WinFilter()
{
InitializeComponent();
FilterViewModel vm = new FilterViewModel();
this.DataContext = vm;
}
private void Cmb_KeyUp(object sender, KeyEventArgs e)
{
CollectionView itemsViewOriginal = (CollectionView)CollectionViewSource.GetDefaultView(Cmb.ItemsSource);
itemsViewOriginal.Filter = ((o) =>
{
if (String.IsNullOrEmpty(Cmb.Text)) return true;
else
{
if (((string)o).Contains(Cmb.Text)) return true;
else return false;
}
});
itemsViewOriginal.Refresh();
// if datasource is a DataView, then apply RowFilter as below and replace above logic with below one
/*
DataView view = (DataView) Cmb.ItemsSource;
view.RowFilter = ("Name like '*" + Cmb.Text + "*'");
*/
}
}
XAML
<ComboBox x:Name="Cmb"
IsTextSearchEnabled="False"
IsEditable="True"
ItemsSource="{Binding DataSource}"
Width="120"
IsDropDownOpen="True"
StaysOpenOnEdit="True"
KeyUp="Cmb_KeyUp" />
I think the CollectionView is what you are looking for.
public ObservableCollection<NdfClassViewModel> Classes
{
get { return _classes; }
}
public ICollectionView ClassesCollectionView
{
get
{
if (_classesCollectionView == null)
{
BuildClassesCollectionView();
}
return _classesCollectionView;
}
}
private void BuildClassesCollectionView()
{
_classesCollectionView = CollectionViewSource.GetDefaultView(Classes);
_classesCollectionView.Filter = FilterClasses;
OnPropertyChanged(() => ClassesCollectionView);
}
public bool FilterClasses(object o)
{
var clas = o as NdfClassViewModel;
// return true if object should be in list with applied filter, return flase if not
}
You wanna use the "ClassesCollectionView" as your ItemsSource for your Combobox
I want to show the Items of a List in a DataGrid. So I did something which should probably work.
First I set the ItemsSource of my DataGrid to the List which is located inside a class, and bound the DataGridTextColumns of my custom Columns to the Items of the List.
The Class:
public class Auftrag : INotifyPropertyCanged
{
private int _id; // ID is an example to show that there is more then `Services`
private List<Services> _services = new List<Services>();
[...] // There are more Fields
[...] // There are more Properties
public int ID { get; set; } // ID is just an Example. My Properties aren't {get; set;} My properties has all the same structure like `Services` Property
public List<Services> Services
{
get => _services;
set
{
if(_services == value) return;
_services = value;
OnPropertyChanged("Services");
}
}
}
The Services class contains the Items which you can see in the XAML of the View where the DataGridTextColumn is bind to.
The ViewModel
In the ViewModel I created a Property which holds the results from my DataBase:
private Auftrag _sAuftrag = new Auftrag();
public Auftrag SAuftrag
{
get => _sAuftrag;
set
{
if (_sAuftrag == value) return;
_sAuftrag = value;
OnPropertyChanged("SAuftrag");
}
}
Im reading the DataBase and add the result to the Properties:
public async Task<bool> LoadSingleAuftrag(int aID)
{
return await Task.Run(() =>
{
// Check SQL Connection, Run Query etc.
[...]
using(var reader = cmd.ExecuteReader())
{
while(reader.Read())
{
SAuftrag.AuftragsId = reader["AuftragsID"] as int? ?? default(int);
SAuftrag.KundenId = reader["KundenID"] as int? ?? default(int);
SAuftrag.Status = reader["Status"] as string;
SAuftrag.CreatedDate = reader["ErstelltAm"] as DateTime? ?? default(DateTime);
SAuftrag.FinishedDate = reader["FertigGestelltAm"] as DateTime? ?? default(DateTime);
SAuftrag.Services.Add(new Services
{
Servicename = reader["Servicename"] as string,
Servicebeschreibung = reader["Servicebeschreibung"] as string,
Einzelpreis = reader["Preis"] as double? ?? default(double),
Anzahl = reader["Anzahl"] as int? ?? default(int),
Preis = Convert.ToDouble(reader["Preis"]) * Convert.ToInt32(reader["Anzahl"])
});
}
// Is working fine, so the Items are sucessfully stored into the `Services` list.
Debug.WriteLine("Anzahl: " + SAuftrag.Services[0].Anzahl);
Debug.WriteLine("Einzelpreis: " + SAuftrag.Services[0].Einzelpreis);
Debug.WriteLine("Gesamtpreis: " + SAuftrag.Services[0].Preis);
}
}
}
The View
<Window.DataContext>
<viewModels:AuftragViewModel />
</Window.DataContext>
[...]
<DataGrid ItemsSource="{Binding SAuftrag.Services}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="Servicename" Header="Servicename" Binding="{Binding Path=Servicename}" />
<DataGridTextColumn x:Name="Beschreibung" Header="Beschreibung" Binding="{Binding Path=Servicebeschreibung}" />
<DataGridTextColumn x:Name="Anzahl" Header="Anzahl" Binding="{Binding Path=Anzahl}" />
</DataGrid.Columns>
</DataGrid>
For me it looks right and should work. But for some Reason I'm getting InvalidOperationException Error.
If I commented the DataGrid the application works and it Bindings are visible (for example the ID Binding). So there is no Instance Error.
Short Version of Error message:
An ItemsControl is not consistent with its element source. For more information, see the inner exception.
May the Properties of the Services class causes the Error?:
Services Class
[..]
private double _preis;
private double _einzelpreis;
[..]
public double Preis
{
get => _preis;
set
{
if (_preis == value) return; // Possible loss of precision while rounding values warning
_preis = value;
OnPropertyChanged("Preis");
}
}
public double Einzelpreis
{
get => _einzelpreis;
set
{
if (_einzelpreis == value) return; // Possible loss of precision while rounding values warning
_einzelpreis = value;
OnPropertyChanged("Einzelpreis");
}
}
View.CodeBehind
In the Code behind, I call a Method to Load data:
private readonly AuftragViewModel _viewModel;
public AuftragsInfo(int aID)
{
InitializeComponent();
_viewModel = (AuftragViewModel) DataContext;
LoadAuftrag(aID);
}
public async void LoadAuftrag(int aID)
{
if (!await _viewModel.LoadSingleAuftrag(aID))
{
Close();
}
}
#ASh wrote in the comments to try out ObservableCollection instead of List.
I don't know why, but after changing the List to ObservableCollection and adding the items via Dispatcher, it's working.
So if someone have this kind of error in the future too, dont use List anymore. I guess it's the best way to use ObservableCollection.
Thanks to #ASh.
I am using a combobox to add data to a datagrid with the coding below. What I want to achieve is accessing all of the 'item' properties/information that's connected to that selected item's id and then setting all of that information to a class (ExtraDisplayItems). How would I go about doing this?
var item = cmbAddExtras.SelectedItem;
if (item != null)
dgAddExtras.Items.Add(item);
Here is my class:
public class ExtraDisplayItems
{
public int ItemId { get; set; }
public string ItemCode { get; set; }
public string ItemDescription { get; set; }
}
What I have gotten to work with databinding is displaying each of the item's 'id' in the datagrid and not any further.
EDIT: I am trying to use the same type that I used in my comboboxes, but I am having a bit of trouble.
I am just giving the extra information if it might help. Here is where I get all the information from my WCF service and then setting the information to my DisplayItems class in my WPF application:
private async Task LoadItems(TruckServiceClient TSC, QuoteOptionType type, ComboBox combobox)
{
List<DisplayItems> displayItems = new List<DisplayItems>();
foreach (var item in await TSC.GetQuoteOptionListAsync(type))
displayItems.Add(new DisplayItems { Id = item.Key, Name = item.Value });
combobox.ItemsSource = (displayItems.ToArray());
}
Then in the method below, I load the data from my LoadItems method into the specific comboboxes:
private async void QuoteWindow_Loaded(object sender, RoutedEventArgs e)
{
using (TruckServiceClient TSC = new TruckServiceClient())
{
await LoadItems(TSC, QuoteOptionType.BodyType, cmbBodyType);
...
await LoadItems(TSC, QuoteOptionType.RearDropSide, cmbRearDropsideHeight);
await LoadItems(TSC, QuoteOptionType.Extras, cmbAddExtras); //Extras
}
//cmbAddExtras.SelectionChanged += cmbAddExtras_SelectionChanged;
}
In my final code snippet I am trying to set the cmbAddExtras.SelectedItem to my Type (wich I think is: QuoteOptionType.Extras).
I am trying to set the SelectedItem as below, but with no clue how to do it :(
var item = cmbAddExtras.SelectedItem as await QuoteOptionType.Extras;
or
var item = cmbAddExtras.SelectedItem as await LoadItems(TSC, QuoteOptionType.Extras, cmbAddExtras);
I am getting the errors:
'await' cannot be used as an identifier within an async method or
lamba expression
&
; expected
Because this call is not awaited, execution of the current method continues before the call is complicated. Consider applying the 'await' operator to the result of the call.
As you can clearly see that I have NO clue what I am doing. Also here is my class where I create my QuoteOptionType class/enum. This is from my WCF application:
[DataContract]
[Flags]
public enum QuoteOptionType
{
[EnumMember]
BodyType,
[EnumMember]
Chassis,
[EnumMember]
PaintColor,
[EnumMember]
DropSide,
[EnumMember]
Floor,
[EnumMember]
RearDropSide,
[EnumMember]
Extras
}
2nd EDIT: Here I am using the Dictionary
public Dictionary<int, string> GetQuoteOptionList(QuoteOptionType optionType)
{
Dictionary<int, string> result = new Dictionary<int, string>();
using (TruckDb db = new TruckDb())
{
switch (optionType)
{
case QuoteOptionType.BodyType:
db.BodyTypes.ToList().ForEach(x => result.Add(x.Id, x.Name));
break;
...
case QuoteOptionType.RearDropSide:
db.RearDropSides.ToList().ForEach(x => result.Add(x.Id, x.Name));
break;
case QuoteOptionType.Extras: // x.StockItem throws out the error: No overload for method 'Add' takes 3 arguments
db.Extras.ToList().ForEach(x => result.Add(x.Id, x.Description, x.StockItem));
break;
default:
throw new ArgumentException("The option that was selected does not have a corresponding list.");
}
}
return result;
}
What you have to do is cast the SelectedItem to the appropiate type. For instance:
var item = cmbAddExtras.SelectedItem as DisplayItems;
if (item != null)
{
var displayItem = new ExtraDisplayItems();
displayItem.ItemId = item.Id;
displayItem.ItemCode = GetCode(item);
displayItem.ItemDescription = item.Name;
DoStuffWithYourDisplayItem(displayItem);
dgAddExtras.Items.Add(item);
}
Even if SelectedItem returns an object, internally that object is actually your class (DisplayItems), so you can cast it and use it normally.
I'm new to WPF and I have some difficulties when I'm trying to populate a ListView with a list of custom objects.
internal class ApplicationCode
{
public int Code { get; set; }
public IEnumerable<string> InstrumentCodes { get; set; }
}
I have a list of ApplicationCode which I set to ItemsSource to a ListView. I need to display the ApplicationCode.Code as a string and for the rest of the columns a check box which can be checked/unchecked depending if the column name is contained in the InstrumentCodes collection.
In order to set the check box I use a converter on databinding:
<DataTemplate x:Key="InstrumentCodeTemplate">
<CheckBox IsEnabled="False" IsChecked="{Binding Mode=OneTime, Converter={StaticResource InstrumentSelectionConverter}}" />
</DataTemplate>
The problem I have is because I can't know which is the current column at the time of cell data binding and I can't set the ConverterParameter.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ApplicationCode appCode = value as ApplicationCode;
return appCode != null && appCode.InstrumentCodes.Contains(parameter.ToString());
}
Small example:
Id | Code1 | Code3 | Code4
--------------------------------
123 | True | False | True
Data for row 1: ApplicationCode.InstrumentCodes {Code1, Code4}
There is a way to find out the column index or name? Or there is another way to solve this problem?
The column name should be nothing more then a visual; which means the needed data should all be residing in the underlying object model. Therefore each row of data is an object.
Perhaps a restructure of your code would suffice which would also remove the need for the converter...keep in mind this is an example to get the idea across and would need modified for actual use.
internal class ApplicationCode
{
private CodeService _codeService = new CodeService();
public int Code { get; set; }
public bool IsValidCode
{
get
{
return _codeService.DoesIntrumentCodeExist(Code.ToString());
}
}
}
internal class CodeService
{
private IEnumerable<string> _instrumentCodes;
public CodeService()
{
//instantiate this in another way perhaps via DI....
_instrumentCodes = new List<string>();
}
public bool DoesIntrumentCodeExist(String instrumentCode)
{
foreach (String code in _instrumentCodes)
{
if (code == instrumentCode)
return true;
}
return false;
}
}
The solution I got so far is to add the columns dynamically and to set the ConverterParameter on every column.
foreach (var instrument in instruments)
{
var column = new GridViewColumn
{
HeaderTemplate = GetColumnHeaderTemplate(instrument),
CellTemplate = GetColumnCellTemplate(instrument),
Header = instrument,
};
view.Columns.Add(column);
}
private static DataTemplate GetColumnCellTemplate(string instrument)
{
var binding = new Binding
{
ConverterParameter = instrument,
Converter = new InstrumentSelectionConverter(),
Mode = BindingMode.OneWay
};
var template = new DataTemplate();
template.VisualTree = new FrameworkElementFactory(typeof(CheckBox));
template.VisualTree.SetBinding(ToggleButton.IsCheckedProperty, binding);
return template;
}
I know this isn't the best solution and I would really appreciate if someone could show me a way to do this directly from .xaml.