Confusion about WPF binding - c#

I am trying to bind a 2D array of buttons arranged in stackpanels to a 2D ObservableCollection...
Yet, I'm afraid I don't understand something very elementary about binding.
My XAML:
<Window.Resources>
<DataTemplate x:Key="ItemsAsButtons">
<Button Content="{Binding}" Height="100" Width="100"/>
</DataTemplate>
<DataTemplate x:Key="PanelOfPanels">
<ItemsControl ItemsSource="{Binding Path=DayNumbers}" ItemTemplate=" {DynamicResource ItemsAsButtons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
...
<ItemsControl x:Name="DaysPanel" Grid.ColumnSpan="7" Grid.Row="2"
ItemTemplate="{DynamicResource PanelOfPanels}"/>
My C# code:
The backend:
/// <summary>
/// Window BE for Calendar.xaml
/// </summary>
public partial class Calendar : Window
{
private CalendarViewModel _vm;
public Calendar()
{
InitializeComponent();
_vm = new CalendarViewModel();
this.DataContext = _vm;
}
}
The ViewModel:
class CalendarViewModel
{
CalendarMonth _displayedMonth;
EventCalendar _calendar;
public CalendarViewModel()
{
_displayedMonth = new CalendarMonth();
}
public ObservableCollection<ObservableCollection<int>> DayNumbers
{
get
{
return _displayedMonth.DayNumbers;
}
}
}
I'm trying to populate the buttons with values from CalendarViewModel.DayNumbers - yet the buttons do not appear. I'm clearly doing something wrong with my binding.

Change all your DynamicResource to StaticResource. This shouldn't stop it working, but might be inefficient at runtime. Have a look this page for WPF resources overview.
Also your ItemsControl is not bound to DayNumbers. Add a binding like so:
<ItemsControl x:Name="DaysPanel" Grid.ColumnSpan="7" Grid.Row="2"
ItemTemplate="{StaticResource PanelOfPanels}"
ItemsSource={Binding DayNumbers}/>
When you set the DataContext on Calendar window you set which object will be the default binding source for the whole window. You didn't specify which property of your ViewModel is bound to the ItemsControl. This is what the code above does.
EDIT Because you are overriding the item template for the ItemsControl and provide a collection container there, you need to provide the ItemsSource for it as well. The syntax {Binding} simply means bind to each member or enumeration, in this case ObservableCollection<int>.
Just to reiterate, the template is exactly that - a template for displaying data. It should be reusable, you should be able to bind it to whatever model you want. A rule of thumb - the data binding to actual data should happen on the control, not the template.

Like Igor said, you need specify ItemsSource={Binding DayNumbers} in outer-most ItemsControl, otherwise, it binds to the DataContext, which is CalendarViewModel and it is not IEnumerable.
Once you do that, it will apply <DataTemplate x:Key="PanelOfPanels"> for each item inside DayNumbers. Note that the DataContext of the DataTemplate in each element in DayNumbers, which is of type ObservableCollection<int>. Here you cannot specify ItemsSource="{Binding Path=DayNumbers}" as DayNumbers is not a valid property in ObservableCollection<int>. Instead, since ObservableCollection<int> is already a IEnumerable, it should be fine not specifying ItemsSource since it will by default bind to DataContext.
Finally, it goes to your inner-most <DataTemplate x:Key="ItemsAsButtons">, and you can put button there as what you did.
Hope it clarifies a little bit. Sorry I don't have the environment to test it out and give you the solution.
Debugging WPF bindings is not straightforward. One tip is you can use dummy converter and set breakpoint in the Convert method to see what it binds.
public class DebugConverter1 : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
#endregion
}
{Binding Converter={StaticResource debugConverter1}}

Related

How to sort ItemsSource dynamically in C#; WPF

I use in XAML an ItemsControl where and in its ItemsSource I make a Binding for an Enum, thus creating a list of RadRadioButton dynamically, and if someday another item is added to this enumerator my code will already create this new button and show it.
<ItemsControl ItemsSource="{Binding MyEnum}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadRadioButton GroupName="GroupMyEnum">
<TextBlock Text="{Binding Converter={StaticResource EnumDescriptionConverter}}"/>
</telerik:RadRadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Today I use a converter that takes the description of the Enum and shows instead of the value of the Enum.
But besides that I would like to change the order in which my list of buttons is generated, is this possible?
Example: If my list needs to be generated elsewhere in the interface, it must be generated in a different order than the enumerator was created.
Whether an Enum has the options A, B, C, D. At one point I would like to show as the first option D instead of A.
Was I clear enough?
Define a converter that takes MyEnum, change its order based on the value of converter's parameter and return the new list with the new order (ConverterParameter is optional, do not set it if you don't want to change the order of the list).
public class MyEnumCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is IEnumerable<MyEnum> input)
{
switch (parameter)
{
case "Case1":
// todo: change the order of input
return input;
case "Case2":
// todo: change the order of input
return input;
}
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Define the converter in your app.xaml (or at one of ItemsControl parents) and use it like this..
<ItemsControl ItemsSource="{Binding MyEnum, Converter={StaticResource MyEnumCollectionConverter}, ConverterParameter=Case1}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadRadioButton GroupName="GroupMyEnum">
<TextBlock Text="{Binding Converter={StaticResource EnumDescriptionConverter}}"/>
</telerik:RadRadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note that you can do the EnumDescriptionConverter work inside MyEnumCollectionConverter, so your TextBlock can be just
<TextBlock Text="{Binding .}"/>
The first thing that pops to mind is sorting the ItemSource itself. I believe you use Linq sort by calling List.Sort() on the source itself and the list of order on the visual layer should be altered aswell.
More information about this can be found here.

WPF binding weirdness

I've used binding many times but this caught me by surprise.
In resources of TabControl I have defined:
<DataTemplate DataType="{x:Type local:ScreenViewModel}">
<Grid>
<Grid.Resources>
<local:converter x:Key="conv"/>
</Grid.Resources>
<di:DisplayView Ime="{Binding Name, Converter={StaticResource conv}, Mode=OneWay}"/>
<TextBlock Text="{Binding Name, Converter={StaticResource conv}, Mode=OneWay}"/>
</Grid>
</DataTemplate>
with converter defined as :
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
just to debug binding process.
di:DisplayView has Ime dependency property of type string and
local:ScreenViewModel class with INotifyPropertyChanged implemented has Name property which I would like to bind to.
Now, the problem with this code is that when I put breakpoint in Convert method of "conv" I see breakpoint is hit twice. First there is predefined string value set in Name and the next time there is empty string. Even if I remove TextBlock binding i still get empty string as value but if I remove di:DisplayView then everything works fine.
I should mention that is UserControl defined in another namespace as ClassLibrary.
Strange behaviour of WPF, isn't it?

Why is this WPF custom DependencyProperty binding on value converter not executing?

I am building what I have learned is basically an accordion control, with a selection mode that ensures that only one section is open at the time. Each section is implemented using Expander controls, so if on Expander is opened, all others should close.
I have done that in the following manner:
Added a property representing the Id of the currently open section, ActiveQuestionId on the view model (which implements INotifyPropertyChanged)
Created a converter inheriting from DependencyObject that is able to convert the ActiveQuestionId to a boolean indicating whether a specific section should be open, by adding a DependencyProperty ControlValue to the converter that indicates which section that it belongs to
Creating a local converter for each section with ControlValue bound to the QuestionId of its section
While the converter methods executes successfully, the problem is the DependencyProperty ControlValue is never set even though it binds successfully to a value and don't raise any errors. I have confirmed this through various debugging. So the result is that all sections are stuck with the default value, rendering the accordion selection behavior I want, useless.
Why is the DependencyProperty binding being ignored? Is it because it is defined within a binding, or something else?
Remarks
Everything is data-driven, and worked great in static a mockup I did before implementing the generic data-driven version. A fully data driven solution is a must, so using one way multi bindings or hardcoded XAML parameters (the solutions I have been able to find for related issues) is not an option.
It is important to note that all other bindings work perfect, so there is no problem DataContext wise. As everything should work (in my mind), this is also why I have not gone the WPF Toolkit Accordion way yet, so please do not suggest this initially (unless it is really the only way). First of, being new to WPF, I would like to understand why this is not working.
XAML (extract - some names changed to obfuscate business meaning - central part is IsExpanded binding):
<ItemsControl ItemsSource="{Binding QuestionSection.QuestionAssignments}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Style="{x:Null}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="{StaticResource Grey400Brush}"
BorderThickness="0 1 0 0">
<Expander Background="{StaticResource Grey200Brush}"
Foreground="Black"
Padding="0"
Margin="0">
<Expander.IsExpanded>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=views:TypeOfParentControl}"
Path="DataContext.ActiveQuestionId"
Mode="TwoWay">
<Binding.Converter>
<converters:TestConverter ControlValue="{Binding QuestionId}"/>
</Binding.Converter>
</Binding>
</Expander.IsExpanded>
<Expander.HeaderTemplate>
<!--Custom Styling Here, All Bindings Work-->
</Expander.HeaderTemplate>
<!--Content Here, All Bindings Work-->
</Expander>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
Converter (simplified)
public class TestConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty ControlValueProperty = DependencyProperty.Register("ControlValue", typeof(short), typeof(TestConverter), new PropertyMetadata(default(short)));
public short ControlValue
{
get { return (short) GetValue(ControlValueProperty); }
set { SetValue(ControlValueProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (short)value==ControlValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? ControlValue : Binding.DoNothing;
}
}
ActiveQuestionId implementation in ViewModel - INotifyPropertyChanged is tested and works, ViewModel is DataContext on Parent UserControl
private short activeQuestionId;
public short ActiveQuestionId
{
get
{
return activeQuestionId;
}
set
{
if (value != activeQuestionId)
{
activeQuestionId = value;
OnPropertyChanged();
}
}
}
The current DataContext value is not inherited down to the TestConverter instance.
You may avoid this complex binding altogether and implement your control by using a ListBox:
<ListBox ItemsSource="{Binding QuestionSection.QuestionAssignments}"
SelectedValuePath="QuestionId"
SelectedValue="{Binding ActiveQuestionId}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Expander IsExpanded="{Binding IsSelected,
RelativeSource={RelativeSource TemplatedParent}}">
...
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

Using datatemplates for converting item source

Lets say I have a ItemsControlwhich is used to render buttons for a list of viewModels
<ItemsControl ItemsSource="{Binding PageViewModelTypes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
CommandParameter="{Binding }" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The PageViewModelTypesare the view models which are available (For example OtherViewModel). For each of the types there is a DataTemplate setup with the according views.
<dx:DXWindow.Resources>
<DataTemplate DataType="{x:Type generalDataViewModel:GeneralViewModel}">
<generalDataViewModel:GeneralView />
</DataTemplate>
<DataTemplate DataType="{x:Type other:OtherViewModel}">
<other:OtherView />
</DataTemplate>
</dx:DXWindow.Resources>
Is there any way of replacing the PageViewModelTypes with the corresponding template types for the ItemsControl within the view?
Bind the button content to the item content and your templates will be resolved to the actual types:
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"
CommandParameter="{Binding }" />
</DataTemplate>
</ItemsControl.ItemTemplate>
Unfortunately, your question is not at all clear. The most common scenario that could fit the vague description you've provided is to have each item in the ItemsControl displayed using a DataTemplate that corresponds to that type.
Let's call that Option A.
But the statement:
replacing the PageViewModelTypes with the corresponding template types for the ItemsControl within the view
…could be construed as meaning you want an entirely different data source for the control. I.e. you want to selectively choose a different value for the ItemsSource property.
Let's call that Option B.
Then later, in the comments, you were asked:
do you want to show the template when the user clicks the relevant button?
…and you responded "yes"! Even though that's a completely different behavior than either of the above two.
Let's call that Option C.
Maybe we can encourage you to provide much-needed clarification. But to do that, it seems most fruitful to start with the simplest, most common scenario. Here is an example of code that implements Option A:
XAML:
<Window x:Class="TestSO28429768ButtonTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestSO28429768ButtonTemplate"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:ColorToBrushConverter x:Key="colorToBrushConverter1"/>
<local:BaseViewModelCollection x:Key="itemsCollection">
<local:StringViewModel Text="Foo"/>
<local:StringViewModel Text="Bar"/>
<local:ColorViewModel Color="Yellow"/>
<local:ColorViewModel Color="LightBlue"/>
</local:BaseViewModelCollection>
<DataTemplate DataType="{x:Type local:StringViewModel}">
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ColorViewModel}">
<Rectangle Width="50" Height="25"
Fill="{Binding Path=Color, Converter={StaticResource colorToBrushConverter1}}" />
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{StaticResource itemsCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
C#:
class BaseViewModelCollection : List<BaseViewModel> { }
class BaseViewModel { }
class StringViewModel : BaseViewModel
{
public string Text { get; set; }
}
class ColorViewModel : BaseViewModel
{
public Color Color { get; set; }
}
class ColorToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new SolidColorBrush((Color)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
As you'll see, the ItemsControl displays the Button instances using its default panel, StackPanel. The Content of each Button is bound to the respective item in the ItemsSource collection, a list containing two each of the StringViewModel class and the ColorViewModel class.
Through defined templates in the window's resources, the content presenter of the button uses the DataTemplate associated with each type of view model. Items corresponding to a StringViewModel get the template for that type, i.e. a TextBlock displaying the text of the view model. Likewise, items corresponding to a ColorViewModel instance get the template that displays a rectangle filled with the color from the view model.
If the above does not exactly address your question (and it may well not), please edit your question to clarify what you are asking:
If the above is close, but not precisely what you wanted, please use the above as a reference and explain how what you want to do is different.
If the above has nothing to do with what you wanted, then ignore it. But do be specific about what you actually want, and use precise terminology. For example, if you really want to replace the ItemsSource with a different collection, then saying you want to replace the PageViewModelTypes collection makes sense. But if not, don't use a phrase that seems to say exactly that!
Of course, if either Option B or Option C more closely match what you are trying to do, go ahead and use those as references for your clarifications.
Finally, please check out the very helpful pages How do I ask a good question? and How to create a Minimal, Complete, and Verifiable example. They have lots of great information about how you can express yourself in a way that will allow others to easily understand what you mean. :)

Transforming a TreeView to Multiple ListBoxes

Given a hierarchical data structure of the form:
public class MyData
{
public string Description { get; set; }
public IEnumerable<MyData> SubNodes { get; set; }
}
I would like to display a series of ListBoxes representing that structure. The flow should be left --> right (e.g. like OS X's Finder), where the left-most ListBox contains the root nodes and the right-most the children.
Multiple items in each ListBox should be selectable, causing the available items in subsequent ListBoxes to update. This is trivial to do with a bit of LINQ and a hard-coded number of ListBoxes, however I wanted the template to be dynamic (i.e. ListBoxes should be added and removed depending on the availability of items). I'd also like the solution to be MVVM-compatible.
This type of control might be something that is already bundled in WPF, however I'm not sure what to search for! Any pointers would be appreciated.
First of all, you do not need to use LINQ (or any C# code at all!) to set the ItemSource on the next ListBox. You can use WPF databinding for this:
<ListBox x:Name="listbox1" ItemsSource="{Binding Path=YourDataCollection}"/>
<ListBox x:Name="listbox2" ItemsSource="{Binding ElementName=listbox1, Path=SelectedItem.SubNodes}" />
<ListBox x:Name="listbox3" ItemsSource="{Binding ElementName=listbox2, Path=SelectedItem.SubNodes}" />
Basically you are binding the next listbox to the previous listbox's SelectedItem's SubNodes (you probably should make sure that the listbox can only select one at a time)
Now to hide the listboxes that do not have any items, you could use a IValueConverter to convert the object to a Visibility state. To do this, create a class:
public class ObjectToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And add some more data binding to your listboxes:
<Window.Resources>
<local:ObjectToVisibilityConverter x:Key="objectToVisible" />
</Window.Resources>
<Grid>
<ListBox x:Name="listbox1" ItemsSource="{Binding ElementName=mw1, Path=dataCollection}"/>
<ListBox x:Name="listbox2" ItemsSource="{Binding ElementName=listbox1, Path=SelectedItem.SubNodes}"
Visibility="{Binding ElementName=listbox2, Path=ItemsSource, Converter={StaticResource objectToVisible}}" />
<ListBox x:Name="listbox3" ItemsSource="{Binding ElementName=listbox2, Path=SelectedItem.SubNodes}"
Visibility="{Binding ElementName=listbox3, Path=ItemsSource, Converter={StaticResource objectToVisible}}" />
</Grid>
Make sure to add your namespace to the window, so the valueconverter can actually be found, in my case I added this:
xmlns:local="clr-namespace:WpfApplication1"
Now this solution works only for a fixed amount of listboxes, but it does not rely on any C# code, aside from the valueconverter. Do you really have no idea about how many sublevels you might need? You could use this technique to add dozens of listboxes to a scrollview for example.
To actually make this dynamic in a UserControl might become quite painful as there would be significant amount of code involved to add listboxes, set their items, delete or hide listboxes when a different root node is selected.
Another possibility might be creating a HierarchicalDataTemplate for the TreeView to customize the look of the treeview. This has the advantage of keeping the treeview, as it is the most useful control for hierarchical data, but changing it's looks should be entirely possible with WPF and it's templates. You might need to change the treeview's controltemplate though (I recommend Blend for editing any type of WPF template)

Categories

Resources