Combobox selection change in XAML - c#

I have a Combobox and two buttons in my UserControl. Is it possible to set those button to change the selected index of the Combobox directly in XAML?
I have done this by two approaches:
Code-behind
private void nextBut_Click(object sender, RoutedEventArgs e)
{
combo.SelectedIndex++;
}
private void prevBut_Click(object sender, RoutedEventArgs e)
{
combo.SelectedIndex--;
}
Or by binding commands to those buttons and define that command in my ModelView.
I have another question about XAML and I really don't know if ask a different question or use this opportunity that you are already reading me! I'm sure it has to be straightforward (at least for WPF gurus around here):
I have a ItemsControl that holds that UserControl, but there may be several or none (because you can create more, or delete). I want a Checkbox outside that is enabled or not depending if there are or not elements in my ItemsContol (disable if there is nothing). I think this can be done with Command Validation but looks difficult to me as I'm new in this world. This also could be done with codebehind but I would like to avoid it. (Like defining a bool property bound to that Checkbox, as write something like if(myItems.Count==0)

I'd rather bind the SelectedItem property to some property in the ViewModel, and bind these buttons to some Commands in the ViewModel. This way keep the state data (selectedItem) in the ViewModel, and can use that to perform any additional logic required, removing the need for code behind.
For the CheckBox, I'd rather put a bool property in the ViewModel, and notify that whenever you add/remove items.
public bool HasItems {get {return Items.Any(); } }
public void AddItem()
{
//...Add Items
NotifyPropertyChanged("HasItems");
}
public void RemoveItem()
{
//...Remove Item
NotifyPropertyChanged("HasItems");
}
This removes the need for an additional converter.

For the checkbox issue, it comes under the generic issue of converting a quantity into a bool. A canonical WPF answer would be bind the checkbox IsChecked property to the collection and route it through an IValueConverter. Here is a converter to do just that...
public class QuantityToBoolConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
try
{
IEnumerable items = value as IEnumerable;
if (items != null)
{
return items.OfType<object>().Any();
}
}
catch
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return null;
}
}
To deploy it, you would need to declare it in your Xaml as a resource...
<Window.Resources>
<converters:QuantityToBoolConverter x:Key="QuantityToBoolConverter"/>
</Window.Resources>
And then bind the IsChecked property to your items and declare the converter...
<ListBox Name="mylb">
</ListBox>
<CheckBox IsChecked="{Binding ElementName=mylb, Path=ItemsSource, Converter={StaticResource
QuantityToBoolConverter}}"></CheckBox>
For the combobox SelectedIndex issue, you can check out the CollectionViewSource docs. Here You can manipulate this in your ViewModel to move the current selection. And there's tons of sample code to examine. Knowing the ICollectionView will serve you well as a WPF developer.

Related

Why is my converter not firing once the property is updated? [duplicate]

This question already has answers here:
C# WPF MVVM Binding not updating
(1 answer)
WPF: What can cause a binding source to not be updated?
(2 answers)
Closed 5 years ago.
To give a short description of my problem, I have a label which I want to show the contents of an ObservableCollection list in the format of "Item1, Item2, Item3, etc".
So my XAML looks like this. Code shortened for readability.
<Window.Resources>
<c:ListToString x:Key="ToList"></c:ListToString>
</Window.Resources>
<Label x:Name="yaxisTxt" Content="{Binding Path=YAxisVariables, Converter={StaticResource ToList}}">
YAxisVariables is the ObservableCollection. My VM behind looks like this, again shortened.
class ChartVM : INotifyPropertyChanged
{
ObservableCollection<string> _yaxisvars;
public ObservableCollection<string> YAxisVariables
{
get
{
return (_yaxisvars);
}
set
{
_yaxisvars = value;
OnPropertyChanged("YAxisVariables");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
}
}
And my converter looks like this. I want the label to show "Drag variable" if the ObservableCollection is empty, and to show the values of the collection if it's not empty.
public class ListToString : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is ObservableCollection<string>)
{
ObservableCollection<string> vars = (ObservableCollection<string>)value;
if (vars.Count > 0)
{
string series = null;
foreach (string var in vars)
{
if (series != null)
series = series + ", " + var;
else
series = var;
}
series.Trim(',');
series.Trim(' ');
return series;
}
else
return "Drag variable";
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
My problem is that when the form is loaded and the list is empty, the converter fires correctly and the label displays "Drag variable", however when the list is updated and the property changes, the converter does not fire.
Am I missing something?
With collections there are 3 kinds of bindings/change notification you need:
Changes to the collection (add, remove). That is the only ones ObservableCollection takes care off
Change notification on the property exposing the collection. OC are notoriously bad to bulk-modify while exposed. So when you want to do bulk modifications, it is better to build it in the View Model, then expose it when fully build.
Change notification for every property of the class you hold in the ObservableCollection
Other things of notice:
You hardcoded the property name string. C# had syntax added so you whould never have to do that. You should definitely be using [CallerMemberName] and similar things to make your code refactoring safe and avoid mispellings.
Actually nothing ever binds to a simple collection. WPF elements only ever bind to CollectionViews. But if you give them a random collection, they do not hesistate to make a CollectionView from it. For advanced things (changing/tracking selected elements, filtering, sorting) you have to take control of that step and make the CollectionView yourself: https://wpftutorial.net/DataViews.html

IsSynchronizedWithCurrentItem not working when ListView is bound to an ICollectionView with a Converter

I have the an ICollectionView in my ViewModel:
public class ContactsPanelViewModel : ViewModelBase
{
private ICollectionView accountContactsView;
public ICollectionView AccountContactsView
{
get => accountContactsView;
set => NotifyPropertyChangedAndSet(ref accountContactsView, value);
}
public ContactsPanelViewModel()
{
AccountContactsView = CollectionViewSource.GetDefaultView(G.AccountContacts); //G.AccountContacts is an IEnumarable<Contact> object
AccountContactsView.SortDescriptions.Add(new SortDescription("FullName_LastNameFirst", ListSortDirection.Ascending));
AccountContactsView.CurrentChanged += NewContactSelected;
AccountContactsView.MoveCurrentToFirst();
}
}
This is the list it is bound to:
<ListView
x:Name="ContactList"
HorizontalAlignment="Left"
ItemsSource="{Binding Path=AccountContactsView, Converter={StaticResource ContactCardConverter}}"
IsSynchronizedWithCurrentItem="True"/>
And this is the converter I created:
public class ContactCardConverter : IValueConverter
{
public object Convert(object Value, Type TargetType, object Parameter, CultureInfo Culture)
{
if (Value is ListCollectionView)
{
List<ContactCard> contactCards = new List<ContactCard>(); //ContactCard is a UserControl
ListCollectionView data = Value as ListCollectionView;
if (data.Count > 0 && data.GetItemAt(0) is Contact)
{
foreach(Contact contact in data)
{
contactCards.Add(new ContactCard(contact));
}
return contactCards;
}
else
{
return null;
}
}
else
{
return null;
}
}
public object ConvertBack(object Value, Type TargetType, object Parameter, CultureInfo Culture)
{
if (Value is ContactCard)
{
return (Value as ContactCard).Contact;
}
else
{
return null;
}
}
}
The issue is that because I am using a converter the CurrentItem in my ICollectionView isn't being synchronized when the ListView selection changes. The converter is definitely the issue since removing the converter makes it work perfectly.
I added in a ConvertBack function thinking it would call that if there was a converter used but it didn't work.
I could theoretically make the ICollectionView of the type ContactCard (which is a UserControl), but that doesn't seem like it is how it should be done since from what I understand the ViewModel shouldn't be dependent on how the View will look. This would also add some complecity as I would need to keep the ICollectionView in sync with my actual collection of Contact objects.
What is the proper way to synchronize a ListView and an ICollectionView when the ListView uses a converter?
The proper way is don't use a converter.
You shouldn't convert your viewmodel-CollectionView to the List(as in your case) or another CollectionView in converter, because otherwise in case you created a CollectionView in converter you will synchronize your view (and IsSynchronizedWithCurrentItem works good) against in converter created collection. List can't be synchronized, because it does not implement IColectionView and has not CurrentItem.
The proper way is don't use a converter and put ContactCard-UserControl to the ListView.ItemTemplate.
<ListView.ItemTemplate>
<DataTemplate>
<youCustomCtlNameSpace:ContactCard Contact="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
If you mandatory wants to use a converter, then IsSynchronizedWithCurrentItem makes no sense and you have to synchronize V and VM by yourself via binding with converter to ListView.SelectedItem and your Contact have to hold reference to the UI-object(It's MVVM conform, if you have no dependencies to View see: Is MVVM pattern broken?). But as I already noted it's not a preferred way!

WPF data binding with member variable of class

I have a class like this.
public class ViewModel
{
public PengChat3ClientSock Sock { get; internal set; }
public ObservableCollection<Room> Rooms { get; internal set; }
public ViewModel(PengChat3ClientSock sock)
{
Sock = sock;
Rooms = new ObservableCollection<Room>();
}
}
and MainWindow.xaml.cs
public ObservableCollection<ViewModel> viewModel { get; set; }
(Of course it is initialized.)
And here is a constructor.
comboBox.ItemsSource = viewModel;
But here, i do not want to use viewModel, only viewModel.Sock.
How can i do this?
There is no viewModel.Sock, as viewModel is a collection of objects of type ViewModel, which contain that property.
Depending on your goals there are different solutions:
You can still bind comboBox to viewModel, but in the item template of the comboBox you can access the Sock property
You can create new collection that will contain only Sock objects ... but then you may have to make sure it is synchronized with the collection of ViewModel objects
Usually you would go about this in a slightly different way:
Create View (UserControl with all the ui elements you need)
Set your 'ViewModel' as DataContext on the newly created View (userControl.DataContext = viewModel)
In the view, where you define your combobox, you can now refer directly to the 'Sock' property on your viewmodel via Bindings.
Code:
<ComboBox ItemsSource="{Binding Sock}"/>
If you just want to set the ItemsSource from code-behind, which is kind of ugly, you might simply add the items in Sock to comboBox.Items - Alternatively you may need to create a new 'Binding' object from code-behind, but this is even uglier:
var binding = new Binding("Sock")
{
Source = viewModel
};
comboBox.ItemsSource = binding;
Please note that I haven't tested the 'Binding in code-behind approach', it's really an anti pattern to do it like that, especially if you're working with MVVM.
Also, you 'Sock' property is a class, and not a collection, so you won't really be able to do this; did you perhaps mean the 'Rooms' property of the ViewModel?
You can only bind an ItemsSource to a type which implements IEnumerable. I have run into this before, and made a converter to convert any object to a list. It's a simple and reusable solution, which leaves the ViewModel logic separated from the view logic:
Converter:
public class ItemToCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new List<object> { value };
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML
<UserControl.Resources>
<local:ItemToCollectionConverter x:Key="ItemToCollectionConverter"/>
</UserControl.Resources>
...
<ComboBox ItemsSource="{Binding Sock, Converter={StaticResource ItemToCollectionConverter}}"/>

Can i delete the elements of my datagrid without changing the datasource?

I want to delete the element of my datagrid without changing my datasource.
When I choose to bind my datagrid to my database Test, it works.
My datagrid shows my 2 columns : "ID" and "Name" with the specific rows.
I can add row to my datagrid and save it to my database.
But now I want to show the name of the columns without any elements, so the user can add rows without seeing the elements of my database.
How can I do this?
I suggest, Create a ObservableCollection of Type Custom Class in which you will have IsVisible property. By default value is false and you can add logic in the xaml if IsVisible = false then hide that row from the grid.
public ObservableCollection<GridInfo> Data { get; set; }
public class GridInfo
{
public bool IsVisible { get; set; }
// other properties will come like this
}
In the XAML you can use converter with the Visibility property of your grid row like below.
Visibility="{Binding IsVisible, Converter={StaticResource VisibilityConverter}}"
And you will have the converter like below:
public class VisibilityConverter : IValueConverter
{
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
bool visibility = !(bool)value;
return visibility ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
Visibility visibility = (Visibility)value;
return (visibility == Visibility.Visible);
}
}
Bind the DataSource of your DataGrid to a property of the type
ObservableCollection<T>
This way the data source gets notified whenever the collection changes.
Assuming that you are correctly Binding to your DataGrid.ItemsSource property, all you need to do to add a new empty row is to add a new item to your data bound collection:
<DataGrid ItemsSource="{Binding Items}" ... />
...
Items.Add(new YourItemtype());
If you are not correctly Binding with the DataGrid.ItemsSource property, then that is what you need to do. Create a property of the type of your data type:
public ObservableCollection<YourItemtype> Items { get; set; }
And of course, make sure that you set your DataContext to the correct data source that contains the property.

Ways to bind Enums to WPF controls like Combobox, TabHeader etc

In my program (MVVM WPF) there are lot of Enumerations, I am binding the enums to my controls in the view.
There are lot of ways to do it.
1) To bind to ComboBoxEdit(Devexpress Control). I am using ObjectDataProvider.
and then this
<dxe:ComboBoxEdit ItemsSource="{Binding Source={StaticResource SomeEnumValues}>
This works fine but in TabControl header it doesn't.
2) So, I thought of using IValueConverter that didnt worked either.
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (!(value is Model.MyEnum))
{
return null;
}
Model.MyEnum me = (Model.MyEnum)value;
return me.GetHashCode();
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
on XAML:
<local:DataConverter x:Key="myConverter"/>
<TabControl SelectedIndex="{Binding Path=SelectedFeeType,
Converter={StaticResource myConverter}}"/>
3) The third way of doing this is to make a behavior dependency property
Something like this
public class ComboBoxEnumerationExtension : ComboBox
{
public static readonly DependencyProperty SelectedEnumerationProperty =
DependencyProperty.Register("SelectedEnumeration", typeof(object),
typeof(ComboBoxEnumerationExtension));
public object SelectedEnumeration
{
get { return (object)GetValue(SelectedEnumerationProperty); }
set { SetValue(SelectedEnumerationProperty, value); }
}
I want to know what is the best way to handle enumerations and binding to it. Right now I am not able to bind tabheader to the enums.
Here's a nicer way of doing it:
On your model, put this Property:
public IEnumerable<string> EnumCol { get; set; }
(Feel free to change the name to whatever suits you, but just remember to change it everywhere)
In the constructor have this (or even better, put it in an initialization method):
var enum_names = Enum.GetNames(typeof(YourEnumTypeHere));
EnumCol = enum_names ;
This will take all the names from your YourEnumTypeHere and have them on the property you'll be binding to in your xaml like this:
<ListBox ItemsSource="{Binding EnumCol}"></ListBox>
Now, obviously, it doesn't have to be a ListBox, but now you're simply binding to a collection of strings, and your problem should be solved.

Categories