I'm a newbie in WPF, so it probably is something very basic that I'm forgetting to do but I can't see what it is.
I have a window with a combobox that display some data, I want the user to select a category in this combobox. It's working partially. The window show the combobox, starting with no selection, then the user choose a item, and it's set, but if the user try to change to other item, nothing works, it keeps the original selected item.
Here's me code:
[Category class]
public class Category {
public long CategoryId { get; set; }
public string Name { get; set; }
public Category MotherCategory { get; set; }
public ICollection<Category> Categories { get; set; }
public int Align { get; set; }
}
[ComboBox XAML]
<ComboBox Grid.Column="1" x:Name="motherCategoryComboBox" Margin="0,6,12,1"
IsSynchronizedWithCurrentItem="True">
<ComboBox.Resources>
<converter:LeftMarginConverter x:Key="LeftMarginConverter" />
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Categories}">
<TextBlock Text="{Binding Path=Name}" Margin="{Binding Path=Align, Converter={StaticResource LeftMarginConverter}}" />
</HierarchicalDataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
[Window code-behind file]
public CategoryWindow()
{
InitializeComponent();
db = new JaspeContext();
categorieslist = db.Categories.ToList();
motherCategoryComboBox.ItemsSource = categorieslist;
Title = "Add category";
}
[The converter]
public class LeftMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double leftMargin = double.Parse(value.ToString());
if (leftMargin != 1)
leftMargin = leftMargin * 9;
return new Thickness(leftMargin, 0, 0, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
}
Need your help. This is making me crazy!
Thanks!!
I hope I understood your question correctly. Is your DataContext a Category object? Sounds to me like you need to bind the SelectedItem property of the ComboBox.
E.g.:
<ComboBox Grid.Column="1" x:Name="motherCategoryComboBox" Margin="0,6,12,1"
IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding MotherCategory , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
It's not your case but since it happened to me I'm publishing this here, to help other people who might stumble upon this issue...
During the comboBox SelectionChangeCommitted() event handler I added the following line:
combobox.Text = combobox.Text.Trim();
what it did is reset the selectedIndex and selectedText properties and didn't allow them to change to the new value due to keyboard or mouse input.
Related
I have a ComboBox like this
<ComboBox
Grid.Column="1"
Padding="5,0,0,0"
DisplayMemberPath="Description"
SelectedItem="{Binding MaxXXAge, Mode=TwoWay, Converter={StaticResource MaxXXAgeToMaxXXAgeMemberConverter}}"
ItemsSource="{Binding ElementName=SettingsXXScrollViewer, Path=DataContext.MaxXXAgeMemberGroup, Mode=OneWay}" />
However, after initialization, the combobox is blank. It actually works fine after this. I can select and show the selected item as expected. It's just the first glance doesn't work. However, I already initialized MaxXXAge and the converter has been triggered. Here is the group
public IReadOnlyList<MaxXXAgeMembers> MaxXXAgeMemberGroup { get { return MaxXXAgeMembers.Options; } }
And this is the definition for MaxXXAgeMembers
public class MaxXXAgeMembers
{
public MaxXXAge MaxXXAge { get; private set; }
public string Description { get; private set; }
public static readonly IReadOnlyList<MaxXXAgeMembers> Options = new ReadOnlyCollection<MaxXXAgeMembers>(new[]
{
new MaxXXAgeMembers { MaxXXAge = MaxXXAge.OneDay, Description = Strings.SettingSync_OneDay},
.......
});
public static MaxXXAgeMembers FromMaxXXAge(MaxXXAge maxXXAge)
{
return Options.First(option => option.MaxXXAge == maxXXAge);
}
}
//Added the Overriding Equals later
public override bool Equals(object obj)
{
if (obj == null || !(obj is MaxEmailAgeMembers))
return false;
return ((MaxEmailAgeMembers)obj).Description.Equals(this.Description);
}
public override int GetHashCode()
{
return this.Description.GetHashCode();
}
The converter is like this
public sealed class MaxEmailAgeToMaxEmailAgeMemberConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return WPSettingsEmailViewModel.MaxEmailAgeMembers.FromMaxEmailAge((MaxEmailAge)value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return ((WPSettingsEmailViewModel.MaxEmailAgeMembers)value).MaxEmailAge;
}
}
Any idea?
It's blank because you don't have anything selected in the first place. If I'm not mistaken, you have to either use SelectedItem to bind your selection or SelectedValue with SelectedValuePath.
I actually never use SelectedValue with SelectedValuePath myself, so after initializing collection of items which ComboBox.ItemSource will be binded to - for example ObservableCollection<Person> Persons {get; set;} - I also set selected item property Person SelectedPerson {get; set;} to one of the values from collection. Then I bind ComboBox.SelectedItem to this property, so on initialization it shows predefined selected value.
I guess you can achieve the same with SelectedValue and SelectedValuePath, but you have to use them together as described here.
I used x:Bind for the ItemResource and added ViewModel inside code behind, and solved this problem.
You ComboBox isn't blank, but it don't know how to render your MaxXXAgeMembers. You should use ItemTemplate to tell this to him. For Ex:
<ComboBox
Grid.Column="1"
Padding="5,0,0,0"
SelectedValue="{Binding MaxXXAge, Mode=TwoWay, Converter={StaticResource MaxXXAgeToMaxXXAgeMemberConverter}}"
ItemsSource="{Binding ElementName=SettingsXXScrollViewer, Path=DataContext.MaxXXAgeMemberGroup, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I'm using ComboBox in my xaml but i'm unable to visualise any data on the view. It just shows empty text file and empty dropdown.
I have tried to debug the problem with help of these tips. However, I haven't been able to solve the issue.
Here's the databindingDebugConverter:
public class DatabindingDebugConverter : IValueConverter
{
public object Convert(object value1, Type targetType, object parameter, CultureInfo culture)
{
Debugger.Break();
return value1;
}
public object ConvertBack(object value2, Type targetType, object parameter, CultureInfo culture)
{
Debugger.Break();
return value2;
}
}
The Value1 is returning in ComboBox Text= case a "Field Device" (object{string})
and on the ItemsSource= value1 is returning object{Device} with the fields of Category and reference to Category1 object holding CategoryId in it.
For the SelectedValue a "Field Device" (object{string}) is once again returned.
Here is the ComboBox xaml:
<ComboBox x:Name="ProductCategoryComboBox" HorizontalAlignment="Right" Height="21.96" Margin="0,20,10.5,0" VerticalAlignment="Top" Width="100"
Text="{Binding DeviceDatabaseViewModel.SelectedDevice.Category, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource debugConverter}}"
IsEditable="False"
ItemsSource="{Binding DeviceDatabaseViewModel.SelectedDevice, Converter={StaticResource debugConverter}}"
SelectedValue="{Binding DeviceDatabaseViewModel.SelectedDevice.Category, Mode=TwoWay, Converter={StaticResource debugConverter}}"
SelectedValuePath="CategoryId"
DisplayMemberPath="Category" />
Similar binding including TextBlock fields within the xaml are working fine and displaying the string values from the SelectedDevice.
EDIT
The selectedDevice is referred from a dataGrid:
private Device _selectedDevice;
public Device SelectedDevice
{
get
{
return _selectedDevice;
}
set
{
if (_selectedDevice == value)
{
return;
}
_selectedDevice = value;
RaisePropertyChanged("SelectedDevice");
}
}
A Combobox is for choosing an Item out of a Collection (for example List or ObservableCollection if you want the UI to recognize changes in the collection).
You do not bind to a collection here:
ItemsSource="{Binding DeviceDatabaseViewModel.SelectedDevice, Converter={StaticResource debugConverter}}"
Instead of binding to the SelectedDevice you would need to bind to an ObservableCollection AllDevices or something like this to which you then could bind the ItemsSource.
Here an example for something you could bind to:
public class DeviceDatabaseViewModel
{
public ObservableCollection<Device> AllDevices
{
get; set;
}
public DeviceDatabaseViewModel()
{
AllDevices = new ObservableCollection<Device>();
AllDevices.Add(new Device { Category = 'Computer', CategoryId = 1 }, new Device { Category = 'Tablet', CategoryId = 2 });
}
}
Then using the following binding:
ItemsSource="{Binding DeviceDatabaseViewModel.AllDevices, Converter= {StaticResource debugConverter}}"
I got a DataGrid that is bound to an object PlacementData (PD). PD has a property "P_Unit".
public class PlacementData
{
public bool PIsChecked { get; set; }
public string PlacementHeader { get; set; }
public string P_NumberOfCases { get; set; }
public int P_Value1 { get; set; }
public int P_Value2 { get; set; }
public int P_Value3 { get; set; }
public int P_Value4 { get; set; }
public int P_Value5 { get; set; }
public string P_Unit { get; set; }
}
In my DataGrid I added a Combobox in DataTemplateColumn.
<DataGridTemplateColumn x:Name="UnitColumn1" Header="Unit" MinWidth="80" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Text="{Binding P_Unit}">
<ComboBoxItem Content="kg/m3" IsSelected="True"/>
<ComboBoxItem Content="gm/cm3"/>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
On start of the window, I set the itemsource with 4 rows with headers added.
private List<PlacementData> datagrid1CollectionData()
{
List<PlacementData> authors = new List<PlacementData>();
authors.Add(new PlacementData()
{
PlacementHeader = "Based On Injection Rate",
});
authors.Add(new PlacementData()
{
PlacementHeader = "Based On Viscosity"
});
authors.Add(new PlacementData()
{
PlacementHeader = "Based On Sheer Thinning"
});
authors.Add(new PlacementData()
{
PlacementHeader = "k"
});
return authors;
}
dataGrid1.ItemsSource = datagrid1CollectionData();
My each row need different values for Unit combo box. For eg., 1 row needs "kg, gm", 2nd needs "meter, cm, feet", 3rd needs "ltr, ml, ton", & 4th needs it to be blank.
How do I set these values ? I think on each row creation, I can create a List and assign that as itemsource to the checkbox. But how is this possible in the above code. Checkbox Itemsource for each row of checkbox ???
I would recommend to use EditCellTemplate but it is up to you and task requirements.
In the combobox in the DataTemplate use custom IValueConverter (I have used PlacementHeader as dependand property, you can use what actually needed or PlacementData itself):
<ComboBox SelectedValue ="{Binding P_Unit}" ItemsSource="{Binding PlacementHeader, Converter={StaticResource DependedValuesConverter}}">
</ComboBox>
and some sample of converter just like idea:
public class DynamicValuesConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
switch (value.ToString())
{
case "Based On Injection Rate":
return new[] { "kg/m3", "gm/cm3" };
case "Based On Viscosity":
return new[] { "some other..." };
}
return new string[0];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
To implement multi selection on combobox you can use some open source CheckComboBox.
EDIT
According to your comment: you can add converter anywhere where it is visible to data template I have added directly to datatemplate just to demonstrate:
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DataTemplate.Resources>
<local:DynamicValuesConverter x:Key="DependedValuesConverter" />
</DataTemplate.Resources>
<ComboBox SelectedValue="{Binding P_Unit}" ItemsSource="{Binding PlacementHeader, Converter={StaticResource DependedValuesConverter}}"></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
"local" has to point to your DynamicValuesConverter namesapce.
I am fairly new to WPF so forgive me if I am missing something obvious. I'm having a problem where I have a collection of AggregatedLabels and I am trying to bind the ItemCount of each AggregatedLabel to the FontSize in my DataTemplate so that if the ItemCount of an AggregatedLabel is large then a larger fontSize will be displayed in my listBox etc. The part that I am struggling with is the binding to the ValueConverter. Can anyone assist? Many thanks!
XAML Snippet
<DataTemplate x:Key="TagsTemplate">
<WrapPanel>
<TextBlock Text="{Binding Name, Mode=Default}"
TextWrapping="Wrap"
FontSize="{Binding ItemCount,
Converter={StaticResource CountToFontSizeConverter},
Mode=Default}"
Foreground="#FF0D0AF7"/>
</WrapPanel>
</DataTemplate>
<ListBox x:Name="tagsList"
ItemsSource="{Binding AggregatedLabels, Mode=Default}"
ItemTemplate="{StaticResource TagsTemplate}"
Style="{StaticResource tagsStyle}"
Margin="200,10,16.171,11.88" />
With your CollectionView in place you might be able to bind to the Groups property, i've never used that, will try it and clarify if possible...
Edit: Allright, here's one way to do it:
The data you bind to needs to be the CollectionView.Groups, the CollectionView should be defined like this:
CollectionView view = (ListCollectionView) CollectionViewSource.
GetDefaultView(LabelData);
view.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
Then you can bind to the respective properties of CollectionViewGroup in code, what you need are probably:
ItemCount
Name
That being said your original binding should work.
Note: You only pass one value to the converter, the ItemCount, thus it should look like this:
public class CountToFontSizeConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
const int minFontSize = 6;
const int maxFontSize = 38;
const int increment = 3;
if ((minFontSize + (int)value + increment) < maxFontSize)
{
return (double)(minFontSize + (int)value + increment);
}
return (double)maxFontSize;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Edit: Further clarifications...
Just add the CollectionView to your ViewModel as a property and create it in its constructor:
public class TagCloudViewModel//:INotifyPropertyChanged
{
public ObservableCollection<AggregatedLabelModel> AggregatedLabels
{get; set;}
public CollectionView AggregatedLabelsView {get; set;} // <-This...
public TagCloudViewModel()
{
var data = new DataAccess();
AggregatedLabels = data.GetData();
//...and this:
AggregatedLabelsView = (ListCollectionView)CollectionViewSource.
GetDefaultView(AggregatedLabels);
AggregatedLabelsView.GroupDescriptions.Add(
new PropertyGroupDescription("Name"));
}
}
Then bind to AggregatedLabelsView.Groups.
I have big problem with databinding.
I cant bind data to children control. I'm really newbie in MVVM and I spend a lot of hours at this example and I have no idea what is wrong with this Code.
Little explanation:
I have MainWindow. It has UserControl to display list of todo.
I want to set my MyWindow class ParentViewModel as DataContext.
DataContext has TodoItemModelView as subdatacontext which must be datacontext of UserControlTodoItems.
<Window x:Class="Repo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Repo="clr-namespace:Repo" Title="Window1" Height="300" Width="300">
<Window.Resources>
<Repo:ParentViewModel x:Key="parentVM"/>
</Window.Resources>
<Window.DataContext>
<StaticResourceExtension ResourceKey="parentVM"/>
</Window.DataContext>
<Grid>
<Repo:UserControlTodoItems DataContext="{Binding Path=todoItemModelView}">
</Repo:UserControlTodoItems>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
class ParentViewModel
{
public TodoItemModelView todoItemModelView { get; set; }
public ParentViewModel()
{
this.todoItemModelView=new TodoItemModelView();
}
}
public class TodoItemModelView
{
public ObservableCollection<TodoItem> todoItems { get; set; }
public TodoItemModelView()
{
ObservableCollection<TodoItem> loadedTodoItems = new ObservableCollection<TodoItem>();
loadedTodoItems.Add(new TodoItem() { Code = "10", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.InProgress, Type = TodoItemType.CollectPhotos });
loadedTodoItems.Add(new TodoItem() { Code = "11", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.Todo, Type = TodoItemType.DescribeOjbect });
loadedTodoItems.Add(new TodoItem() { Code = "12", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.Accomplshed, Type = TodoItemType.CollectVideos });
todoItems = loadedTodoItems;
}
}
<UserControl x:Class="Repo.UserControlTodoItems"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Repo="clr-namespace:Repo" Height="auto" Width="auto">
<UserControl.Resources>
<Repo:TodoItemStatusConverter x:Key="TodoItemStatusConverter"/>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=todoItems}" Name="lbTasks">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Status, Converter={StaticResource TodoItemStatusConverter}}"/>
<TextBlock Text="{Binding Path=Type}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
public UserControlTodoItems()
{
InitializeComponent();
}
I correct this.
I must add one question:
is there any simple way to inform parentmodel, of change checkbox at listbox?
this is a converter:
public class TodoItemStatusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
TodoItemStatus todoItemStatus = (TodoItemStatus)value;
if (todoItemStatus == TodoItemStatus.Accomplshed)
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool) value)
{
return TodoItemStatus.Accomplshed;
}
else
{
return TodoItemStatus.InProgress;
}
}
This is class TodoItem:
public class TodoItem
{
public TodoItemType Type { get; set; }
public TodoItemStatus Status { get; set; }
public string Code { get; set; }
public string ObjectCode { get; set; }
public ObjectType ObjectType { get; set; }
}
Why is the binding for your "lbTasks" Listbox just "{Binding}" and not "{Binding Path=todoItems}"
I'm really taking a quick glance at your code here.. you seem to be passing the todoItemModelView as a DataContext properly, but never inform the listbox where in that data context it will find its items.
You may also want to use an ObservableCollection for the list in the VM so you can add and remove todo's in a way the GUI can respond to
<CheckBox IsChecked="{Binding Path=Status, Converter={StaticResource TodoItemStatusConverter}}"/>
This implies there is a property on ToDoItemViewModel called Status - but there isn't! Rethink your ToDoItemVm class to just be a wrapper for a toDoItem (ie, public ToDoItemVm(ToDoItem model) and get that array of items into the PArentVm (do use ObservableCollection and bind it to the list box. Also add a SelectedToDoItem property on the ParentVm. So your binding for the list box includes something like
ItemsSource="{Binding ToDoTems}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedToDoItem, Mode=TwoWay}"
Then expose that Status property on your ToDoItemVm, have the class implement INPC, and raise PropertyChanged in the setter.
It may take some work to sort it out, so feel free to ask more questions as you go. The converter idea is fine.
HTH,
Berryl