WPF data binding with member variable of class - c#

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}}"/>

Related

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!

Binding Visibility propety of text block to Stack.Any

I have a TextBlock as follow:
<TextBlock Text="You don't have any more items." Visibility="{binding}"
and in code behind I defined a Stack called items as follow:
private Stack<Item> _items;
How do I bind the text visibility in xaml to visible when _item.Any is false?
There are several steps to achieving what you want to do and they are all described here
You need to create a value converter similar to this;
public class EmptyCollectionToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = (Stack<int>) value;
return collection.Any() ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then you need to add a reference to is in your resource dictionary in your xaml like this;
<views:EmptyCollectionToVisibilityConverter x:Key="EmptyCollectionToVisibilityConverter"/>
Finally bind your property in your view model to the visibility of your control and give the binding the converter like this;
Visibility="{Binding Items, Converter={StaticResource EmptyCollectionToVisibilityConverter}}"
Your property will probably need to be an observableCollection (which will mean changing the value converter example I gave you slightly.
I'd probably go with:
private Stack<Item> _items;
// bind to this property using converter
public bool IsVisible => !(_items?.Any(...) ?? false);
You shouldn't expose your _stack directly, but e.g. use methods to do something (because you need to rise notification every time you push/pop an item):
public void PushItem(Item item)
{
_items.Push(item);
OnPropertyChanged(nameof(IsVisible)); // implement INotifyPropertyChanged
}

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.

Displaying data differntly across viewmodels, without changing model data

Another problem has been bugging me lately, and I am quite sure it is my lacking WPF/MVVM skills that prevents me from seing the obvious. I am sure the solution is a simple one, however, I am unsure how to implement it.
I am developing in WPF/C# and I am using the MVVM designpattern.
For simplicity, I am boiling the question down to it's most basic components.
The scenario:
I have a Model, only containing an integer.
I have a Parent ViewModel, displaying this integer.
I have two child viewmodels, incorparated in the parent viewmodel, both displaying this integer. On one of the viewmodel, I have a Command, incrementing the value of the integer with 1. The value is changed in the model, which implements the INotifyPropertyChanged therefore making the second ViewModel aware of the change, so it can update accordingly.
So far everything works fine.
However, I am interested in a new feature, and I cannot get it to work. Say, that I on my second viewmodel want to display the integer, but I want to transform the way the data is displayed. This should be done, however, without changing the data in the model. Should the data in the model change, the transformed data will change accordingly.
As an example, lets assume that the integer is 5. On the second viewmodel, I want to display the integer + 2, meaning 7.
Then the data is changed to 6 from the first viewmodel, meaning that the property on the second viewmodel changes to 8 automatically.
How is this implemented?
A few codepieces, to illustrate what the system looks like so far:
The Model:
public DataModel()
{
data = new Data();
data.Value = 2;
}
public Data data { get; set; }
And the data Class:
public class Data : INotifyPropertyChanged
{
private int m_Value;
public int Value
{
get { return m_Value; }
set
{
if (m_Value != value)
{
m_Value = value;
OnPropertyChanged("Value");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
The mainviewmodel
public class MainViewModel : ViewModelBase
{
readonly DataModel _dataModel;
public MainViewModel()
{
_dataModel = new DataModel();
ViewModel1 = new 1ViewModel(this);
ViewModel2 = new 2ViewModel(this);
}
public 1ViewModel ViewModel1 { get; set; }
public 2ViewModel ViewModel2 { get; set; }
public Data Data
{
get { return _dataModel.data; }
}
}
And here is the way the ChildViewmodels binds themselves to the Data object
public class 1ViewModel : ViewModelBase
{
private MainViewModel _mainViewModel;
public 1ViewModel(MainViewModel mainViewModel)
{
_mainViewModel = mainViewModel;
}
public Data Number
{
get { return _mainViewModel.data; }
}
}
And on the view1, i have bound the Number property like this
<TextBlock Text="{Binding Path=Number.Value}" />
Again, I want to be able to create a second property on the viewmodel, which displays the transformed data, based on, but without changing the original data, and which updates together with the data being updated.
Preferably, it should be a kind of converter-method, which converts the data to the new data.
Hope you are able to help.
The ViewModel should hold the data ready for display but not knowledgeable on the way in which it will be displayed. If you want to change the way in which the data is displayed in your View, for that you would use a Converter. This would allow you to use the same ViewModel for multiple Views and have different appearances.
In addition wrapping a ViewModel inside a ViewModel is not necessarily the way you want to go. A ViewModel generally has an associated View. If you have no view for the ViewModel, treat the data as typical classes and proceed with your single ViewModel.
Use an IValueConverter:
public class AddConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int)value + (int)parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int)value - (int)parameter;
}
}
And in your XAML, add the resource:
And update your binding:
I used a ConverterParameter here, but you can hardcode the value if so desired. Might want to add some checks to your converter as well, as it will throw an exception if the types are not correct.

Categories