How to properly bind to element property in WPF - c#

I have little problem with binding. I have stackpanel in my xaml which has some elements in children collection. Second i have textblock which shows count of elements in stackpanel. It is done by binding that way
<TextBlock Text="{Binding Children.Count, ElementName=CommentsContainer, Mode=OneWay, StringFormat=({0})}" />
<StackPanel x:Name="CommentsContainer"></StackPanel>
It works fine for first-time but if is something added to stackpanel children collection dinamically textblock text is not updated. I mean that collection count does not implement inotifypropertychange, but how do something like this properly?

You asked "how [to] do something like this properly". The WPF way of doing it would be to have a collection of items implemented as a property on your Window or ViewModel or whatever, then bind an ItemsControl to that collection.
For example, if you had a collection of strings:
public ObservableCollection<string> MyItems { get; private set; }
// elsewhere in the same class...
MyItems = new ObservableCollection<string>();
MyItems.Add("first");
MyItems.Add("second");
MyItems.Add("etc");
ObservableCollection<T> is a good collection class to use for WPF as notification of any changes made to the collection (such as adding or removing items) will be pushed to any observers of the collection (such as WPF's binding system).
To see these items in your View (eg. Window, UserControl, etc), you would use a control that can display a list of items (one derived from ItemsControl) and bind that control to the list property, like so:
<Window ... >
<StackPanel>
<ItemsControl ItemsSource="{Binding MyItems}" />
<TextBlock Text="{Binding MyItems.Count}" />
</StackPanel>
</Window>
ObservableCollection<T> does implement INotifyPropertyChanged so the Count property will always reflect the actual number of items in the list.
You don't have to have a list of strings of course, they could be any kind of object. Similarly, you don't have to use an ItemsControl but could use something like a ListBox or ListView instead (they both derive from that base control class). Furthermore, you might want to look into data templating as this can be used to change the visual appearance of the items in the ItemsControl.

Related

Display a List<T> by using x:Bind?

I'm currently trying to figure out how to use the x:Bind stuff in UWP/xaml.
I'm learning more everyday and the app I'm curently writing is getting way easier to manage since I can implement the MVVM patterns now.
But there is one thing I encountered now...
I have a List<T> which I want to display in my UI. How can I bin this list while using x:Bind?
Or do I have to convert it into something else first?
Best Regards,
Daniel
You can certainly use a List<T> for binding, but usually ObservableCollection<T> is preferable, because it also allows the UI to observe list changes, as opposed to List<T> that will not update after bound first. You can create an ObservableCollection from List using the constructor:
ObservableCollection<T> data = new ObservableCollection<T>( list );
In any case, you first have to create a property (but fields are also supported with x:Bind) in your view model:
public ObservableCollection<T> Data { get; } = new ObservableCollection<T>();
Remember that binding connects to the instance, so if you would set a new instance to Data property, it the binding would not update. For that to work you need to implement INotifyPropertyChanged interface on your view mdoel and raise PropertyChanged event in the setter.
Now, to display the items in your UI you need a list control like ListView or GridView and bind it to your collection:
<ListView ItemsSource="{x:Bind Data, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- your template -->
<TextBlock Text="{Binding SomePropertyOfT}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can find a thorough walk through data binding in documentation as well.
If your desire is to have a collection which might change over time, and you want the UI to be notified of such modifications, you should not utilize List<T>!
Use instead ObservableCollection<T>, which implements the INotifyPropertyChanged and INotifyCollectionChanged which is the heart of the MVVM, allowing the ViewModel/View to communicate between each other.
Such collection automatically handles the addiction/removal of elements automatically for you!
Anyway, here is a simple example, showing how you can use a List to communicate with your View.
MainPage.xaml.cs
public MainPage()
{
this.InitializeComponent();
RandomList = new List<string>() {
"random 1", "random 2", "random 3"
};
}
List<string> RandomList { get; set; }
MainPage.xaml:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ComboBox ItemsSource="{x:Bind RandomList}"
PlaceholderText="List of Random things"/>
</Grid>
Here we defined a {x:Bind } to populate the ItemsSource's dependency property, which accepts a Collection.
This Binding is defined with the default binding Mode, which is OneTime for compiled bindings. For instance, if you were to perform Bindings with {Binding } markup, the default mode is OneWay;
Since you created a Collection with List rather than ObservableCollection, there would be no reason to specify any other mode than the default, since you haven't implement a mechanism for your CLR collection to actually notify the View of an update.

Getting parent of new tab after adding to bound TabControl (mvvm)

I'm adding a close button to my tabs using the following guide:
http://www.codeproject.com/Articles/84213/How-to-add-a-Close-button-to-a-WPF-TabItem
This has become a problem because the event uses the 'parent' of the added tab to remove that tab from the tabcontrol. I'm binding the tab control using mvvm, so the parent property is apparently not being set and giving me a null reference exception for the parent when the event tries to remove from it.
Here's the binding so you get the idea:
<TabControl Name="tabControl" Margin="0,22,0.2,-5.2" ItemsSource="{Binding Tabs}" Background="#FF4C76B2"/>
Heres where the tabs are being added.
private void AddTab(object tabName)
{
ClosableTab newTab = new ClosableTab();
newTab.Title = "title?";
//newTab.Header = tabName;
TextBox test = new TextBox();
test.Text = "CONTENT (" + tabName + ") GOES HERE";
newTab.Content = test;
Tabs.Add(newTab);
OnPropertyChanged("Tabs");
}
Here is the event where the null reference is taking place:
void button_close_Click(object sender, RoutedEventArgs e)
{
((TabControl)this.Parent).Items.Remove(this);
}
As I see it there are two options:
try to find another way to remove the tab (without the parent
property)
try to find a way to somehow set the parent property (which cant be
done directly, it throws a compiler error)
That doesn't sound like MVVM to me. We work with data, not UI elements. We work with collections of classes that contain all of the properties required to fulfil some requirement and data bind those properties to the UI controls in DataTemplates. In this way, we add UI controls by adding data items into these collections and let the wonderful WPF templating system take care of the UI.
For example, you have a TabControl that we want to add or remove TabItems from... in a proper MVVM way. First, we need a collection of items that can represent each TabItem:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<string>), typeof(TestView));
public ObservableCollection<string> Items
{
get { return (ObservableCollection<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
I'm just using a DependencyProperty because I knocked this up in a UserControl and I'm just using a collection of strings for simplicity. You'll need to create a class that contains all of the data required for the whole TabItem content. Next, let's see the TabControl:
<TabControl ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ItemTemplate}" />
We data bind the collection to the TabControl.ItemsSource property and we set the TabControl.ItemTemplate to a Resource named ItemTemplate. Let's see that now:
xmlns:System="clr-namespace:System;assembly=mscorlib"
...
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type System:String}">
<TabItem Header="{Binding}" />
</DataTemplate>
This DataTemplate defines what each item in our collection will look like. For simplicity's sake, our strings are just data bound to the TabItem.Header property. This means that for each item we add into the collection, we'll now get a new TabItem with its Header property set to the value of the string:
Items.Add("Tab 1");
Items.Add("Tab 2");
Items.Add("Tab 3");
Note that I included the System XML Namespace Prefix for completeness, but you won't need that because your DataType will be your own custom class. You'll need more DataTemplates too. For example, if your custom class had a Header property and a Content property, which was another custom class, let's say called Content, that contained all of the properties for the TabItem.Content property, you could do this:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type YourPrefix:YourClass}">
<TabItem Header="{Binding Header}" Content="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:Content}">
<YourPrefix:SomeUserControl DataContext="{Binding}" />
</DataTemplate>
So this would give you TabItems with Headers set and Content that comes from SomeUserControl which you could design. You don't need to use UserControls, you could just add more UI controls to either DataTemplate. But you will need to add more controls somewhere... and more classes and properties, always remembering to correctly implement the essential INotifyPropertyChanged interface.
And finally, to answer your question in the proper MVVM way... to remove a TabItem, you simply remove the item that relates to that TabItem from the collection. Simple... or it would have been if you really had been using MVVM like you claim. It's really worth learning MVVM properly as you'll soon see the benefits. I'll leave you to find your own tutorials as there are many to chose from.
UPDATE >>>
Your event handling is still not so MVVM... you don't need to pass a reference of any view model anywhere. The MVVM way is to use commands in the view model. In particular, you should investigate the RelayCommand. I have my own version, but these commands enable us to perform actions from data bound Buttons and other UI controls using methods or inline delegates in the view model (where action and canExecute in this example are the CommandParameter values):
<Button Content="Close Tab" Command="{Binding CloseTabCommand}"
CommandParameter="{Binding}" />
...
public ICommand CloseTabCommand
{
get { return new ActionCommand(action => Items.Remove(action),
canExecute => canExecute != null && Items.Contains(canExecute)); }
}
So whatever view model has your Tabs collection should have an AddTabCommand and a CloseTabCommand that add and remove items from the Tabs collection. But just to be clear, for this to work properly, your ClosableTab class should be a data class and not a UI control class. Use a DataTemplate to specify it if it is a UI control.
You can find out about the RelayCommand from this article on MSDN.

Binding ListBoxItem properties when ListBox is bound to ObservableCollection

I have a ListBox bound to an ObservableCollection:
<ListBox ItemsSource="{Binding ObservableCollectionOfFoos}" />
The ObservableCollection contains instances of Foo, Foo implements INotifyPropertyChanged.
I'm trying to bind properties of the ListBoxItems to properties of Foo.
Here is what I tried:
<DataTemplate DataType="{x:Type local:Foo}" >
<TextBlock Content="{Binding PropertyOfFoo}" Background="{Binding AnotherPropertyOfFoo}"/>
</DataTemplate>
this works, but, the problem is that I only have access to the properties of the TextBlock and not the containing ListBoxItem, so, for example, Background only changes the color around the text and not the whole entry. I have a feeling I'm using the wrong tool for the job here.
a point in the right direction would be very appreciated.
The DataTemplate specifies the template (UI presentation) of the content of the list box item. What you need to style is the item container itself which can be done via the ItemContainerStyle property of the ListBox.

Looking for guidance for where to put some code in a WPF MVVM application

First time attempting MVVM, looking for clarity on where to put some code.
My main view will need to bind to a list that will be holding 1 to many UserControls.
Would the List exist in the ViewModel or the Model? From what I'm reading, the model contains properties typically that the View binds to via the ViewModel. I don't see how that would work for this, the Model would not need to know about the List of UserControls which is a list of a View(UserControl), I may be making this harder than needed, but I'm still wrapping my mind around where code should be placed and I want to understand it. Thanks for any guidance, or if I did not explain myself well please let me know.
Your UserControls should have a ViewModel (Let's call it ItemViewModel by now).
Your MainViewModel should have an ObservableCollection<ItemViewModel>.
Then your view should have an ItemsControl (or one of its derivatives such as ListBox) for which the ItemsSource property will be bound to the ObservableCollection.
And then the ItemsControl should define an ItemTemplate that contains your UserControl.
This is the right way to do what you're describing with WPF / MVVM.
Example (pseudocode):
MainViewModel:
public class MainViewModel
{
public ObservableCollection<ItemViewModel> Items {get;set;}
}
MainView:
<Window>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<my:UserControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Keep in mind that each instance of UserControl will have it's DataContext set to the corresponding item in the source collection.
Therefore, if your ItemsViewModel looks something like this:
public class ItemsViewModel
{
public string LastName {get;set;}
public string FirstName {get;set;}
//INotifyPropertyChanged, etc.
}
your UserControl can be defined like this:
<UserControl>
<StackPanel>
<TextBox Text="{Binding LastName}"/>
<TextBox Text="{Binding FirstName}"/>
</StackPanel>
</UserControl>
You shouldn't need a list of UserControls. What you would likely have is your View binding to a List of items in your ViewModel. For example, create a ListBox and set it's ItemsSource to your ViewModel's list.
To create a your user control for each item, you would need to create a DataTemplate for the type in your list and specify your UserControl and you can give any bindings inside that usercontrol to the item.
The ListBox will then use the DataTemplate to create a UserControl for each item in the list.

Reapplying template for ListBox item programatically

Basically, I have a list of colors and a defined datatemplate for listbox item:
<DataTemplate x:Key="colorItemDataTemplate">
<Border x:Name="borderInner" BorderBrush="Black" BorderThickness="1" Background="{Binding Brush}" Width="11" Height="11" />
</DataTemplate>
Now, when I add a bunch of items into the listbox and then set the ListBox.ItemsSource property to my List, the listbox is filled correctly.
There is also a slider with its appropriate event handler. Within the event handler, Brush property of one of the items from the listbox is changed. Since the appearance of the item depends on the Brush Property, listbox should reflect the change.
I could reset the ItemsSource property, but then all items have their templates applied and with more than 200 items in the listbox, this is pretty slow.
So, is there any way to refresh the template for only one item from the listbox?
Thanx
I'm not sure I follow. If you've bound the Background to the property, changing the property should automatically udpate the background of the ListBoxItem. If you're not seeing that, make sure you are either using a DependencyProperty or implementing INotifyPropertyChanged.
You could use a binding converter. In the converter class you could have some logic like
(pseudo-code)
if (ListBoxItem.IsSelected)
return SpecialColorFromSlider
else
return NormalListBoxColor

Categories

Resources