Procedurally generating text with inline <span> XAML from a binding - c#

I am building a C# WPF application using MVVM to decouple the view from the business logic.
One of my data sources is a legacy application that outputs ANSI color codes and I would like to faithfully reproduce these in the UI using textblocks, or boxes, or whatever is most appropriate.
I have been able to write a very simple converter to turn the ANSI codes into elements with styles to set the color:
"\x1b[30mblack\x1b[37mwhite"
becomes
#"<span style=""color:#000000"">black<span style=""color:#BBBBBB"">white</span></span>"
However I can not find a way to bind this text to my views while also reproducing the colors.
Most examples online focus on situations where the text is always going to be the same, so colors can be hardcoded in the XAML and the text is bound to different spans/runs. This is not going to work for me.
I have looked briefly at using the WebBrowser control but this seems like a very big hammer for such a small problem. I'm going to have hundreds of these labels in a list, so performance is a concern.
Finally I found a solution whereby XAML may be written into the view at runtime, but I have had no luck whatsoever getting my converted string to load as valid XAML: Richtextbox wpf binding
This seems to be one of those cases of WPF making a simple problem extremely difficult. Is there a good solution that I am overlooking?

What exactly are you trying to parse here? Your text says you're trying to parse XAML yet the code you've provided is HTML?
If you can stick with XAML then it's relatively straightforward. First of all you'll need some XAML data in your view model:
public string[] Spans { get; } = new string[]
{
"<Span Foreground=\"Blue\">Hello World!</Span>",
"<Span Foreground=\"Green\">Goodbye World!</Span>"
};
Whenever you have a list of things to draw in WPF you usually use an ItemsControl. However, instead of a list you'll probably want a WrapPanel. A converter can be used to convert each element of the list into a span which you can wrap that in a parent ContentControl:
<ItemsControl ItemsSource="{Binding Spans}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding Path=., Converter={StaticResource SpanConverter}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Then all you need is the converter itself to take the raw XAML, parse it and return the resulting object. You'll also need to add the default (empty) namespace along with "x" so that the parser know where to find the objects it's deserializing:
public class SpanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
return XamlReader.Parse(value?.ToString(), context) as ContentElement;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
Result:
Now take all this and throw it out, because parsing raw XAML is not how you do this in MVVM. To do this properly you would create a view model for your spans like so:
public class SpanViewModel
{
public string Text { set; get; }
public Color Foreground { set; get; }
// .. plus any other fields you want
}
And you would create a list of those instead:
public SpanViewModel[] Spans { get; } = new SpanViewModel[]
{
new SpanViewModel{Text="Hello World!", Foreground=Colors.Blue},
new SpanViewModel{Text="Goodbye World!", Foreground=Colors.Green}
};
We're going to use a DataTemplate, so get rid of the ItemTemplate from your ItemsControl:
<ItemsControl ItemsSource="{Binding Spans}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
And create a DataTemplate for your SpanViewModel, binding to the relevant properties (you'll need to use TextBlock because Span doesn't support binding):
<Window.Resources>
<DataTemplate DataType="{x:Type local:SpanViewModel}">
<TextBlock Text="{Binding Text}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding Foreground}" />
</Setter.Value>
</Setter>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</Window.Resources>
There are many variations on this, but this should be enough to get you going.

Related

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. :)

hide or remove border of wpf button in C# code

I have a stackpanel named "mystack" in my xaml file and I am adding buttons in it dynamically from the .cs file and want to remove the border of buttons in C# .cs file
what I really want is to populate this stackpanel with the buttons coming from a list of string
thanks in advance
xaml:
<Grid HorizontalAlignment="Left" Height="227" Margin="10,10,0,0" Grid.Row="2"
VerticalAlignment="Top" Width="530">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Name="mystack" HorizontalAlignment="Left" Grid.Row="2"
VerticalAlignment="Top" Width="520"/>
</ScrollViewer>
</Grid>
.cs:
public List<String> Schools()
{
List<String> l = new List<string>();
l.Add("SST");
l.Add("SBE");
l.Add("SSH");
return l;
}
I agree with HighCore, you generally do not want to manipulate the UI elements in your code.
To remove the Border of the buttons you can set a Button's BorderThickness property to "0" in Xaml or to new Thickness(0) in the code-behind.
i.e.
myButton.BorderThickness = new Thickness(0);
EDIT:
Okay, I noticed your updated question. I would create a property that stores your list of schools and bind to it in a way similar to this:
public List<string> Schools
{
get { return _schools; }
set { _schools = value; }
}
Somewhere you need to set the DataContext of the control to your class containing the Schools property. If you are dynamically updating the list of Schools you'll need to implement INotifyPropertyChanged so the UI knows when to update. And then your Xaml would look something like this:
<ItemsControl ItemsSource="{Binding Schools}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" BorderThickness="0" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl>
You can't remove button's border like: btn.BorderThicknes=new Thickness(0);
See this: Removing button's border
The fast Fix:
What I had to do to effectively hide the button border - and due to the button control template I believe which utilizes and changes Button border (i.e. even if you remove it it'd draw it on some trigger I believe)...
...was to set BorderBrush="Transparent" as well (I always do BorderThickness as well but I'm guessing it's not needed - only for visual/layout look'n'feel)
i.e. setting thickness alone is not enough.
I'm really not sure that's the bets way to do it, or actually I'm
quite sure there must be something smarter - but I always end up with
that.
The Right Way:
Proper way - and recommended - is to write your own Button template -
based on the Microsoft official one - or base it on it - and do what
you need w/o borders.
For the code behind/C#:
You really don't need that as per your changed question - do what others suggested already
the best way to do this is :
<Style TargetType="Button">
<Style.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="0"/>
</Style>
</Style.Resources>
</Style>
what I really want is to populate this stackpanel with the buttons
coming from a list of string
That's called a ListBox:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" BorderThickness="0"/>
<!-- Whatever other customizations to the button -->
</DataTemplate
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public class ViewModel
{
public ObservableCollection<string> Items {get;set;}
public ViewModel()
{
Items = new ObservablecCollection<string>();
Items.Add("String1");
Items.Add("String2");
Items.Add("String3");
}
}
You need to learn the MVVM pattern.

Storing properties of objects in C#/WPF

I'm allowing user to drag/drop some objects from a toolbox and of course each object has a unique id. As soon as object is used, let's say placed on a grid or canvas, I need to show its properties so I need an array of objects where each object can hold its own properties.
Can you give me some advice and direction on how to implement a class to handle multiple objects while each object can hold on to let's say 10 properties?
The best solution is to use a PropertyGrid control; your application looks similar to Visual Studio and your implementation will be similar to that.
Have a look at this SO question for available PropertyGrid options you have -
Is there a Property Dialog control that i can use in my WPF App?
Now you can define a class for each control and declare normal CLR properties for that control; properties you don't want to display in PropertyGrid can be marked with BrowsableAttribute and PropertyGrid will honor that.
In case you want more control over what properties are displayed, you can create your own custom attribute and modify PropertyGrid implementation to use that attribute and display properties marked with this attribute.
Can you give me some advice and direction on how to implement a class
to handle multiple objects while each object can hold on to let's say
10 properties?
There is no need for you to implement such a class. The way I would handle this problem would be to have a common base class for all the objects in the toolbox (ToolboxItem for example) which only exposes properties and functionality common to all items in the toolbox.
public abstract class ToolboxItem
{
public string Name { get; set; }
public Point Position { get; set; }
}
You can then derive your specific items from this class E.G. TextToolboxItem and RectangleToolboxItem (or whatever you want). The derived classes can then expose only the properties they require.
public class TextToolboxItem : ToolboxItem
{
public string Text { get; set; }
}
public class RectangleToolboxItem : ToolboxItem
{
public Rect Bounds { get; set; }
}
To store these you could just use a generic collection such as:
ObservableCollection<ToolboxItem> items = new ObservableCollection<ToolboxItems>();
As long as the items derive from ToolboxItem they can all be held within the single collection and the individual properties can all be bound to using WPF's data binding features.
You can then create and expose the data in the following way:
public partial class MainWindow : Window
{
private ObservableCollection<ToolboxItem> items;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
items = new ObservableCollection<ToolboxItem>
{
new TextToolboxItem { Name = "primaryText",
Text = "Hello world",
Position = new Point(40, 130) },
new TextToolboxItem { Name = "secondaryText",
Text = "Hello world (again)",
Position = new Point(200, 30) },
new RectangleToolboxItem { Position = new Point(50,300),
Name = "Rect1",
Bounds = new Rect(0, 0, 150, 85) },
};
}
public ObservableCollection<ToolboxItem> Items { get { return items; } }
}
To display this information in the user interface I would do the following:
Use a grid to split the view into two sections. The first is where the properties of the selected item will be displayed and the second displays the 'design surface'
Use a ContentPresenter to display the properties of the selected item.
Use a ListBox with a custom ItemsPanel and ItemContainerStyle to 'draw' your items onto the design surface.
Use a DataTemplate to tell WPF how to render each item in both the 'property grid' and the 'design surface' (This post describes how to use a different DataTemplate for different objects).
The xaml required to achieve this is shown below:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="7*" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding ElementName=listBox, Path=SelectedItem}"
Margin="5">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type this:TextToolboxItem}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Position}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type this:RectangleToolboxItem}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Position}"/>
<TextBlock Text="{Binding Bounds}"/>
</StackPanel>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
<ListBox x:Name="listBox" Grid.Column="1"
Margin="5" ItemsSource="{Binding Items}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type this:TextToolboxItem}">
<TextBox Text="{Binding Text}"
Margin="10"/>
</DataTemplate>
<DataTemplate DataType="{x:Type this:RectangleToolboxItem}">
<Rectangle Width="{Binding Bounds.Width}"
Height="{Binding Bounds.Height}"
Stroke="DarkRed" Fill="Pink"/>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Position.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Position.Y}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
The end result looks like this:
Notice that the properties of the selected item are shown in the left hand section of the window.
Now this solution is currently very crude but does demonstrate a starting point for you to develop this further. Ideas for improvement include:
Re-factoring the code into a viewModel so that it is MVVM compliant.
Handling drag and drop of the items on the 'design surface'.
Changing the `ContentPresenter' for a property grid to give you much richer support for displaying and editing the properties of the selected object.

Confusion about WPF binding

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

Categories

Resources