WPF, RichTextBox, SelectionBrush, not SelectionBackColor - wpf

In a WPF app RichTextBox, I'm trying to find a way to provide a background color for various words within the box. In the System.Windows.Forms version of the RichTextBox, there was a very simple way to do this:
richTextBox1.SelectionBackColor = color;
richTextBox1.AppendText(word);
However, the System.Windows.Controls version of RichTextBox only has SelectionBrush, and this same method does not work.
Is a background color for different words in the RichTextBox possible?

You can work with the FlowDocument within the RichTextBox
<RichTextBox>
<RichTextBox.Document>
<FlowDocument>
<Paragraph>
<Run Background="Red">Hello World</Run>
<LineBreak/>
<Run Background="Green">This is a colored</Run>
<Run>text.</Run>
</Paragraph>
</FlowDocument>
</RichTextBox.Document>
</RichTextBox>
Edit regarding your comments: a (nearly) full example showing two different approaches.
No matter what you end up doing to present your text in the UI, you should have some sort of logic that creates a suitable data model of your highlighted text. The following example uses a collection of TextFragment where each fragment is optionally marked as highlighted.
public class TextFragment
{
public TextFragment(string text, bool isHighlighted)
{
this.Text = text;
this.IsHighlighted = isHighlighted;
}
public string Text { get; private set; }
public bool IsHighlighted { get; private set; }
}
Also, for the sample I use a class TextEntry to manage original text, search text and the resulting text fragments. Note I inherit from a BaseViewModel class which implements some helper functions for INotifyPropertyChanged related things. The helper function bool SetProperty<T>(ref T store, T value, [CallerMemberName]string propertyName = null) will check whether value and store are equal, potentially update the store with value and raise a property changed notification. The return value indicates, whether the value was really different/changed.
public class TextEntry : BaseViewModel
{
public TextEntry()
{
TextParts = new ObservableCollection<TextFragment>();
}
private void UpdateTextParts()
{
TextParts.Clear();
if (string.IsNullOrEmpty(SearchText))
{
TextParts.Add(new TextFragment(OriginalText, false));
return;
}
int startAt = 0;
do
{
int next = OriginalText.IndexOf(SearchText, startAt, StringComparison.CurrentCultureIgnoreCase);
if (next == -1)
{
TextParts.Add(new TextFragment(OriginalText.Substring(startAt), false));
return;
}
else
{
if (next != startAt)
{
TextParts.Add(new TextFragment(OriginalText.Substring(startAt, next - startAt), false));
}
// add highlighted part
TextParts.Add(new TextFragment(OriginalText.Substring(next, SearchText.Length), true));
startAt = next + SearchText.Length;
}
} while (startAt < OriginalText.Length);
}
private string _OriginalText;
public string OriginalText
{
get { return _OriginalText; }
set
{
if (SetProperty(ref _OriginalText, value))
{
UpdateTextParts();
}
}
}
private string _SearchText;
public string SearchText
{
get { return _SearchText; }
set
{
if (SetProperty(ref _SearchText, value))
{
UpdateTextParts();
}
}
}
public ObservableCollection<TextFragment> TextParts { get; private set; }
}
You can create a multi-part text in the UI by appending multiple textblocks with different text settings in a horizontal StackPanel. This way, the text parts can be managed by an ItemsControl. Alternatively, you can use the RichTextBox with its Document property, but this needs some more handling in code behind.
Some initialization code in the main window and a method to update the document for the RichTextBox example:
public MainWindow()
{
InitializeComponent();
var vm = new TextEntry();
grid1.DataContext = vm;
// this trigger works, but don't ask about efficiency for a bigger application
vm.TextParts.CollectionChanged += TextParts_CollectionChanged;
}
void TextParts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ObservableCollection<TextFragment> data = sender as ObservableCollection<TextFragment>;
var doc = richTextBox1.Document;
var paragraph = new Paragraph();
paragraph.Inlines.AddRange(data.Select(x =>
{
var run = new Run(x.Text);
if (x.IsHighlighted)
{
run.Background = Brushes.LightCoral;
}
return run;
}));
doc.Blocks.Clear();
doc.Blocks.Add(paragraph);
}
And the XAML content of the window:
<Grid x:Name="grid1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Original Text: " Margin="3"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Searched Word: " Margin="3"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Highlighted Text: " Margin="3"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Highlighted Text2: " Margin="3"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding OriginalText, UpdateSourceTrigger=PropertyChanged}" Margin="3"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding SearchText,UpdateSourceTrigger=PropertyChanged}" Margin="3"/>
<ItemsControl Grid.Row="2" Grid.Column="1" ItemsSource="{Binding TextParts}" Margin="3" IsTabStop="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Background" Value="LightCoral"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<RichTextBox Grid.Row="3" Grid.Column="1" x:Name="richTextBox1" Margin="3" VerticalAlignment="Top" IsReadOnly="True"/>
</Grid>
Expected Program:
Two lines with text inputs. One for the original text, one for the searched text.
3rd line showing the original text with search highlights as TextBlocks.
4th line showing the original text with search highlights as RichTextBox.

Related

How can I DataBind a textbox from the parent window with values from the child with MVVM?

I just took over a project from another programmer who is no longer here. It was created using the MVVM Pattern (using the MVVM Light toolkit). I am new to MVVM and have been trying to learn the basics fast. Currently I am having trouble getting a selected value from a Child Window back to the Parent Window.
From another post on SO I learned that I should use the same ViewModel for both the parent and the child so I think I have the basics right. However I have not been able to get the selected values back to the parent. Below is a sample set of code similar to the production code.
My ViewModel for both pages is here
public class MainViewModel : ViewModelBase
{
private Vendor selectedVendor = null;
List<Vendor> vendors;
public MainViewModel()
{
OpenVendorWindowCommand = new RelayCommand(VendorSelect);
VendorSelectedCommand = new RelayCommand(VendorSelected);
LoadVendors();
}
public ICommand OpenVendorWindowCommand { get; private set; }
public ICommand VendorSelectedCommand { get; private set; }
void VendorSelect()
{
Messenger.Default.Send(new NotificationMessage("SelectVendor"));
}
public Vendor SelectedVendor
{
get { return selectedVendor; }
set
{
if (selectedVendor != value)
{
selectedVendor = value;
RaisePropertyChanged();
}
}
}
void VendorSelected()
{
Console.WriteLine(SelectedVendor.VendorName);
}
public List<Vendor> Vendors
{
get
{
return vendors;
}
set
{
if (vendors != value)
{
vendors = value;
RaisePropertyChanged();
}
}
}
private void LoadVendors()
{
DataTable dt = new DataTable();
dt = Vendor.GetVendors();
Vendors = new List<Vendor>();
foreach (DataRow row in dt.Rows)
{
Vendors.Add(new Vendor()
{
VendorID = Convert.ToInt32(row["VendorID"]),
VendorCode = Convert.ToString(row["VendorCode"]),
VendorName = Convert.ToString(row["VendorName"])
});
}
}
}
I am at the point that the Child Window opens and I am able to select a vendor from a ListBox. After the selection I press a button (VendorSelectedCommand) and it is at that point I want the textbox on the Parent Window to be filled with the SelectedVendor.VendorName value.
This is the XAML from my Child Window
<StackPanel VerticalAlignment="Center">
<ListBox
Height="200"
Margin="5"
HorizontalAlignment="Stretch"
Background="GhostWhite"
ItemsSource="{Binding Vendors}"
SelectedItem="{Binding Path=SelectedVendor, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="3">
<StackPanel Margin="15">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="175" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
FontWeight="SemiBold"
Foreground="Black"
Text="{Binding VendorName}" />
<TextBlock
Grid.Column="1"
FontWeight="SemiBold"
Foreground="Black">
<Run Text=" (" />
<Run Text="{Binding VendorCode}" />
<Run Text=") " />
</TextBlock>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding VendorSelectedCommand}" Content="Send Vendor Back" />
</StackPanel>
And lastly this is the XAML for the Parent Window with what I think is the correct binding
<StackPanel VerticalAlignment="Center">
<TextBox Margin="10" Text="{Binding SelectedVendor.VendorName}" />
<Button
Margin="10"
Command="{Binding OpenVendorWindowCommand}"
Content="Select Vendor" />
</StackPanel>
I have tried every possible combination of Binding Syntax that I can think of and have tried multiple different ways in the code behind to catch and bind it but have not been able to get it right. What is missing from my ViewModel to make this work?
Edit For clarity (and in response to a comment) I am adding the DataContext, which I had in the Constructor of the Views.
public partial class VendorView : Window
{
private MainViewModel _vm = null;
public VendorView()
{
InitializeComponent();
_vm = new MainViewModel();
DataContext = _vm;
}
}
Edit #2 I am opening the second page with this. This is very simple sample app with only two pages so I didn't want to get bogged down with navigation until I have a better handle on Binding.
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "SelectVendor")
{
var vendorView = new VendorView();
vendorView.ShowDialog();
}
}

WPF MVVM pattern

i am doing a simple project with mvvm pattern. its about one list that every row has one textbox and delete button and at
buttom we have one text box and add button like this:
name1 buttondelete
name2 buttondelete
name3 buttondelete
.
.
textbox buttonadd
with click the buttondelete the row should delete and with click bottonadd the text of textbox should insert in list as new
row.
i have three layer Sepand.WPFProject.Model , Sepand.WPFProject.ViewModel , Sepand.WPFProject.View;
in model i have context and repository and model (here my model is Category that have Name & ID property) class. repository is like this:
public class ModelRepository<T>
where T : class
{
ModelDbContext ctx = new ModelDbContext();
public IQueryable<T> GetAll()
{
IQueryable<T> query = ctx.Set<T>();
return query;
}
public void Add(T entity)
{
ctx.Set<T>().Add(entity);
ctx.SaveChanges();
}
public void Delete(T entity)
{
ctx.Set<T>().Remove(entity);
ctx.SaveChanges();
}
in viewModel i have categoryViewModel class like this:
public class CategoryViewModel
{
ModelRepository<Category> repository = new ModelRepository<Category>();
ObservableCollection<Category> categories = new ObservableCollection<Category>();
Category category = new Category();
public ObservableCollection<Category> GetAll()
{
IQueryable<Category> categoryRepository = repository.GetAll();
foreach (Category Category in categoryRepository)
categories.Add(Category);
return categories;
}
public ObservableCollection<Category> GetAllCategories
{
get { return GetAll(); }
}
public string TxtName
{
get { return category.Name; }
set { category.Name = value; }
}
in View in code behind i have
this.DataContext = new CategoryViewModel();
and in XAML i have
<Window.Resources>
<DataTemplate x:Key="CategoryTemplate">
<Border Width="400" Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4">
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Width="300" Margin="5" Text="{Binding Path=Name}"></TextBlock>
<Button Name="btnDeleteCategory" Width="50" Margin="5" Click="btnDeleteCategory_Click" >-</Button>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
.
.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Column="0" Grid.Row="0" Name="lstCategory" ItemTemplate="{StaticResource CategoryTemplate}" ItemsSource="{Binding Path=GetAllCategories}"/>
<StackPanel Margin="5" Grid.Column="0" Grid.Row="1" Orientation="Horizontal">
<Label Content="Name : "/>
<TextBox Name="TxtName" Text="{Binding Path=TxtName ,Mode=TwoWay}" Width="260"/>
<Label Width="50"/>
<Button Width="50" Content="+" Name="btnAddCategory" Click="AddCategory_Click" />
</StackPanel>
</Grid>
</Grid>
and now when i run app the listbox populated with data from database; but i could not write code for addbutton and
delete button;
could anyone tell me what should i do?
and why i could not bind the text of textbox in list to TxtName Property of CategoryViewModel class ?
i mean here
<TextBlock Width="300" Margin="5" Text="{Binding Path=Name}"></TextBlock>
when i write Binding Path=TxtName the list box would not show data but with Binding Path=Name
it shows data from database
Your question is a bit scattered. But I'll try address what I think are your issues.
You say in the code behind you have:
this.DataContext = new CategoryViewModel();
But nothing else.
First thing to do with checking why your button isn't working would be to see what action it is performing. Your XAML states it's using a click event:
btnDeleteCategory_Click
Where's that? Is it not in your code-behind too? It might be that you've not got anything and that's why your button isn't doing anything - you've not instructed it to do anything!
In MVVM you should be binding your button using Commands in your ViewModel, similarly to how you bind data to Properties in your ViewModel.
You need something like:
Command="{Binding Path=DeleteCommand}"
in your view, and:
public ICommand DeleteCommand
{
get { return new DelegateCommand<object>(FuncToCall, FuncToEvaluate); }
}
private void FuncToCall(object context)
{
//this is called when the button is clicked - Delete something
}
private bool FuncToEvaluate(object context)
{
//this is called to evaluate whether FuncToCall can be called
//for example you can return true or false based on some validation logic
return true;
}
Binding to TxtName might not be working because it does not implement/call PropertyChanged.

WPF Control losing focus when clicking on a tab

On a tabcontrol I have several tabpages, on one of the tabs there is a textbox in the content.
This textbox is content bound with a simple Path=PropertyName and UpdateSourceTrigger=LostFocus. The reason I am using LostFocus is I trap the Lost focus event of the Textbox and possibly reformat the text. This is a "time" textbox and if they enter "0900", I want to reformat to "09:00". This part works great when I press the tab key to move to the next control, but if I type "0900" then press one of the other tabs, I hit the lost focus and re-format the value in the textbox, BUT the bind never gets called to update my object. When I come back to the tab, the value is blanked out (or reset to the original value on the object)
Any ideas why textbox does not trigger the Binding update when changing tab page?
Note: this also happens with a regular textbox that does wire to the lost focus event. It seems to have something to do with click on the tab.
[[Added Code ]]
More notes:
1. I am dynamically creating the tabs and controls on the tab (not sure if that has something to do with it or not)
2. I am using the Prism libraries
MainWindow Xaml
<Window.Resources>
<DataTemplate DataType="{x:Type ctrls:myTextBoxDef}">
<Grid Width="300">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding LabelText}" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding DocValue,
Mode=TwoWay,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=LostFocus}"
/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsTabStop="False"
ItemsSource="{Binding Tabs, Mode=OneWay}"
SelectedItem="{Binding SelectedTab,
Mode=TwoWay,
NotifyOnSourceUpdated=True}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Margin="18,14,22,0"
Text="{Binding HeaderText}" />
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Content -->
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<AdornerDecorator Grid.Column="0">
<ItemsControl Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsTabStop="False"
ItemsSource="{Binding Controls,
Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Grid.Column="0"
Margin="10,5,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</AdornerDecorator>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Main Window Code Behind
public partial class MainWindow : Window
{
private DataContextObject obj = new DataContextObject();
public MainWindow()
{
InitializeComponent();
myTextBoxDef txt1 = new myTextBoxDef(obj, "Textbox 1", "TAB1TextBox1");
myTextBoxDef txt1b = new myTextBoxDef(obj, "Textbox 1 value", "TAB1TextBox1");
myTextBoxDef txt2 = new myTextBoxDef(obj, "Textbox 2", "TAB1TextBox2");
myTextBoxDef txt2b = new myTextBoxDef(obj, "Textbox 2 value", "TAB1TextBox2");
obj.Tabs.Add(new myTabDef("Tab 1", new ObservableCollection<myTextBoxDef>() { txt1, txt2 }));
obj.Tabs.Add(new myTabDef("Tab 2", new ObservableCollection<myTextBoxDef>() { txt1b, txt2b }));
obj.SelectedTab = obj.Tabs[0];
this.DataContext = obj;
}
}
Supporting objects
public class DataContextObject : NotificationObject
{
List<myTabDef> _tabs = new List<myTabDef>();
public List<myTabDef> Tabs
{
get
{
return _tabs;
}
}
private myTabDef _item;
public myTabDef SelectedTab
{
get
{ return _item; }
set
{
_item = value;
this.RaisePropertyChanged("SelectedItem");
}
}
private string _txt1 = "";
public string TAB1TextBox1
{
get { return _txt1; }
set
{
_txt1 = value;
this.RaisePropertyChanged("TAB1TextBox1");
}
}
private string _txt2 = "";
public string TAB1TextBox2
{
get { return _txt2; }
set
{
_txt2 = value;
this.RaisePropertyChanged("TAB1TextBox2");
}
}
private string _txt3 = "";
public string TAB2TextBox1
{
get { return _txt3; }
set
{
_txt3 = value;
this.RaisePropertyChanged("TAB2TextBox1");
}
}
}
public class myTabDef
{
public myTabDef(string tabText, ObservableCollection<myTextBoxDef> controls)
{
HeaderText = tabText;
_left = controls;
}
public string HeaderText { get; set; }
private ObservableCollection<myTextBoxDef> _left = new ObservableCollection<myTextBoxDef>();
public ObservableCollection<myTextBoxDef> Controls
{
get
{
return _left;
}
}
}
public class myTextBoxDef : NotificationObject
{
public myTextBoxDef(NotificationObject bound, string label, string bindingPath)
{
LabelText = label;
Path = bindingPath;
BoundObject = bound;
BoundObject.PropertyChanged += BoundObject_PropertyChanged;
}
public string LabelText
{
get;
set;
}
public NotificationObject BoundObject
{
get;
set;
}
public string DocValue
{
get
{
return PropInfo.GetValue(BoundObject, null) as string;
}
set
{
PropInfo.SetValue(BoundObject, value, null);
}
}
protected virtual void BoundObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals(Path))
{
this.RaisePropertyChanged("DocValue");
}
}
public string Path
{
get;
set;
}
private PropertyInfo pi = null;
protected PropertyInfo PropInfo
{
get
{
if (pi == null && BoundObject != null && !string.IsNullOrEmpty(Path))
{
PropertyInfo[] properties = BoundObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
pi = properties.Where((prop) => string.Compare(prop.Name, Path, true) == 0).FirstOrDefault();
}
return pi;
}
}
}
We have found a solution. I came cross this set of postings
https://groups.google.com/forum/#!topic/wpf-disciples/HKUU61A5l74
They talk about a control called TabControlEx. Towards the bottom (5th from the bottom) you will see a posting by Sacha Barber that has a zip file with an example.
It solved all our problems we were having.
here is also another link where the code for the Class is posted
http://updatecontrols.codeplex.com/discussions/214434

Horizontal stretch list item in WP7 with grouping

I want to implement ListBox Grouping on WP7. I found this article is very useful. Actually I made the grouping works. But I got a problem with ListItem horizontal stretch. I guess I need to set ItemContainerStyle and change HorizontalContentAlignment as Stretch. But it doesn't work for this case (if set the ItemTemplate directly, it works). Any suggestions? Thanks a lot!
Here is the code, the ListItem is supposed to be stretched, but it's centered instead.
C#:
public class GroupingItemsControlConverter : IValueConverter
{
public object Convert(object value, Type tagetType, object parameter, CultureInfo culture)
{
var valueAsIEnumerable = value as IEnumerable;
if (null == valueAsIEnumerable)
{
throw new ArgumentException("GroupingItemsControlConverter works for only IEnumerable inputs.", "value");
}
var parameterAsGroupingItemsControlConverterParameter = parameter as GroupingItemsControlConverterParameters;
if (null == parameterAsGroupingItemsControlConverterParameter)
{
throw new ArgumentException("Missing required GroupingItemsControlConverterParameter.", "parameter");
}
var groupSelectorAsIGroupingItemsControlConverterSelector = parameterAsGroupingItemsControlConverterParameter.GroupSelector as IGroupingItemsControlConverterSelector;
if (null == groupSelectorAsIGroupingItemsControlConverterSelector)
{
throw new ArgumentException("GroupingItemsControlConverterParameter.GroupSelector must be non-null and implement IGroupingItemsControlConverterSelector.", "parameter");
}
// Return the grouped results
return ConvertAndGroupSequence(valueAsIEnumerable.Cast<object>(), parameterAsGroupingItemsControlConverterParameter);
}
private IEnumerable<object> ConvertAndGroupSequence(IEnumerable<object> sequence, GroupingItemsControlConverterParameters parameters)
{
// Validate parameters
var groupKeySelector = ((IGroupingItemsControlConverterSelector)(parameters.GroupSelector)).GetGroupKeySelector();
var orderKeySelector = ((IGroupingItemsControlConverterSelector)(parameters.GroupSelector)).GetOrderKeySelector();
if (null == groupKeySelector)
{
throw new NotSupportedException("IGroupingItemsControlConverterSelector.GetGroupSelector must return a non-null value.");
}
// Do the grouping and ordering
var groupedOrderedSequence = sequence.GroupBy(groupKeySelector).OrderBy(orderKeySelector);
// Return the wrapped results
foreach (var group in groupedOrderedSequence)
{
yield return new ContentControl { Content = group.Key, ContentTemplate = parameters.GroupStyle };
foreach (var item in group)
{
yield return new ContentControl { Content = item, ContentTemplate = parameters.ItemStyle };
}
}
}
public object ConvertBack(object value, Type tagetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("GroupingItemsControlConverter does not support ConvertBack.");
}
}
public class GroupingItemsControlConverterParameters
{
public DataTemplate GroupStyle { get; set; }
public DataTemplate ItemStyle { get; set; }
public IGroupingItemsControlConverterSelector GroupSelector { get; set; }
};
public abstract class IGroupingItemsControlConverterSelector
{
public abstract Func<object, IComparable> GetGroupKeySelector();
public virtual Func<IGrouping<IComparable, object>, IComparable> GetOrderKeySelector() { return g => g.Key; }
}
public class GroupingItemsControlConverterSelector : IGroupingItemsControlConverterSelector
{
public override Func<object, IComparable> GetGroupKeySelector()
{
return (o) => (o as ItemViewModel).Group;
}
}
XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.Resources>
<DataTemplate x:Key="GroupHeaderTemplate">
<Border BorderBrush="Yellow" BorderThickness="1" Margin="12,3,12,12" Padding="6" VerticalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Number}" HorizontalAlignment="Left" Margin="6,0,0,0" FontSize="22" Foreground="White"/>
<TextBlock Grid.Column="1" Text="{Binding Name}" HorizontalAlignment="Right" Margin="0,0,6,0" FontSize="22" Foreground="White"/>
</Grid>
</Border>
</DataTemplate>
<DataTemplate x:Key="CustomItemTemplate">
<Grid Margin="12,3,12,12" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding Number}" HorizontalAlignment="Left" Margin="6,0,0,0" FontSize="22" Foreground="White"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Name}" HorizontalAlignment="Right" Margin="0,0,6,0" FontSize="22" Foreground="White"/>
</Grid>
</DataTemplate>
<local:GroupingItemsControlConverter x:Key="GroupingItemsConverter" />
<local:GroupingItemsControlConverterSelector x:Key="GroupingItemsSelector" />
<local:GroupingItemsControlConverterParameters x:Key="GroupingItemParameters"
GroupStyle="{StaticResource GroupHeaderTemplate}"
ItemStyle="{StaticResource CustomItemTemplate}"
GroupSelector="{StaticResource GroupingItemsSelector}"
/>
<Style TargetType="ListBoxItem" x:Key="CustomItemContainerStyle">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</Grid.Resources>
<ListBox x:Name="TheListBox"
ItemsSource="{Binding Items, Converter={StaticResource GroupingItemsConverter}, ConverterParameter={StaticResource GroupingItemParameters}}"
ItemContainerStyle="{StaticResource CustomItemContainerStyle}" />
</Grid>
ListBox grouping? You should consider using the LongListSelector from the Silverlight Toolkit. And to simplify the binding for that, you can use the LongListCollection collection type (Check the entire example, for details).
Then you can simply create apps that groups values, for example like this:

How do I bind list items to an Accordian control from the Silverlight 3 Toolkit?

I have a list of objects in a model. I wish to show elements of the DTO's in the list in my AccordianItem panels. The model is like this:
public class MyModel
{
public List<AnimalDTO> Items { get; set; }
public MyModel()
{
Items = new List<AnimalDTO>
{
new AnimalDTO() {Title = "Monkey", ImageUri = "Images/monkey.jpg"},
new AnimalDTO() {Title = "Cow", ImageUri = "Images/cow.jpg"},
};
}
}
public class AnimalDTO
{
public string Title { get; set; }
public string LongDescription { get; set; }
public string ImageUri { get; set; }
public string NavigateUri { get; set; }
}
I want to show the image in the background image of AccordianItems and lay the LongDescription over a portion of the image.
If I hard code it, I can get the image in the AccordianItem thus...
<layoutToolkit:AccordionItem x:Name="Item2" Header="Item 2" Margin="0,0,10,0" AccordionButtonStyle="{StaticResource AccordionButtonStyle1}" ExpandableContentControlStyle="{StaticResource ExpandableContentControlStyle1}" HeaderTemplate="{StaticResource DataTemplate1}" BorderBrush="{x:Null}" ContentTemplate="{StaticResource CarouselContentTemplate}">
<layoutToolkit:AccordionItem.Background>
<ImageBrush ImageSource="Images/cow.jpg" Stretch="None"/>
</layoutToolkit:AccordionItem.Background>
</layoutToolkit:AccordionItem>
When I try it with a binding like <ImageBrush ImageSource="{Binding Path={StaticResource MyContentTemplate.ImageUri}}" Stretch="None"/> or if I try it with <ImageBrush ImageSource="{Binding Path=Items[0].ImageUri}" Stretch="None"/>
, it throws XamlParseException.
Edit:
I'm able to get some binding of the text over hard-coded images with the following StaticResource (NOTE: I'm hard-coding Items[2], I'm not sure how to index it)
<DataTemplate x:Key="CarouselContentTemplate">
<Grid Width="650" Height="420">
<Grid.RowDefinitions>
<RowDefinition Height="0.476*"/>
<RowDefinition Height="0.524*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
x:Name="Title"
Text="{Binding Items[2].Title}"
Foreground="Black" FontSize="12"></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0"
x:Name="LongDescription"
Text="{Binding Items[2].LongDescription}"
TextWrapping="Wrap"FontSize="8"></TextBlock>
</Grid>
</DataTemplate>
Is there a way to index the Items collection in the DataTemplate? Furthermore how do I get the Image to bind rather than hard-coding them in each AccordianItem? Any help in the right direction would be appreciated, most especially how to bind and lay text over an image.
To bind to a collection it must be referenced with ItemsSource="{Binding Items}", where in this case Items is my collection MyModel.Items
<layoutToolkit:Accordion
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ExpandDirection="Right"
Style="{StaticResource AccordionStyle1}"
AccordionButtonStyle="{StaticResource AccordionButtonStyle1}"
MaxHeight="420" MaxWidth="800"
ItemsSource="{Binding Items}" Margin="8,0,-8,-12" Grid.Row="3"
>
<layoutToolkit:Accordion.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"></TextBlock>
</DataTemplate>
</layoutToolkit:Accordion.ItemTemplate>
Note that a collection should be bound with ItemsSource, which is plural as a mnemonic. and individual members of elements are bound within control of <layoutToolkit:Accordian.ItemTemplate> Here I am showing MyCollection.Title in a TextBlock control. I shall update this with full code or a link to my blog for a full example later.

Resources