WPF Listview Item Vertical Positioning with Canvas Overlap Issue - c#

I have a word addin which has a WPF custom task pane. In that I have a Listview. I need to dynamicaly update the position of each listview item when word document scroll down. but some of them can be having same canvas.top(vertical Y) value.Then those items getting overlap.
I do not need to overlap those,I need as listview need to align one after another.
screen sample
Code Sample XAML..
<listViewTool:ListView x:Name="Results" Margin="0" BorderThickness="0" DockPanel.Dock="Top" Background="WhiteSmoke"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Results}"
SelectionMode="Single">
<listViewTool:ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Control.HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Control.Margin" Value="0"/>
<Setter Property="Control.Padding" Value="0"/>
<Setter Property="Control.BorderThickness" Value="0"/>
<Setter Property="IsSelected" Value="{Binding Path=Selected, Mode=TwoWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
<Setter Property="Canvas.Left" Value="0"/>
<Setter Property="Canvas.Width" Value="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListView}},Path=ActualWidth}" />
</Style>
</listViewTool:ListView.ItemContainerStyle>
<listViewTool:ListView.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="CanvasMain" HorizontalAlignment="Stretch" Height="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListView}},Path=ActuaHeight}"
ClipToBounds="True" />
</ItemsPanelTemplate>
</listViewTool:ListView.ItemsPanel>
</listViewTool:ListView>
Thanks.

I've made a simple solution, which looks like this:
Idea is to store height of items and recalculate position of all items when height of either is changed.
Item height is item template border ActualHeight. For each item: Y is initial position, Offset is calculated/used to bind (based on Height of items before current item).
Solution contains some debugging code (Text property and binding) and is not a pure MVVM (but you didn't ask for one):
xaml:
<ListView ItemsSource="{Binding Items}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Canvas.Left"
Value="0" />
<Setter Property="Canvas.Top"
Value="{Binding Offset}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border CornerRadius="10"
BorderThickness="1"
BorderBrush="Gray"
SizeChanged="Border_SizeChanged">
<Expander Header="{Binding Text}">
<Grid Height="50" />
</Expander>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Height="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}"
ClipToBounds="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListView>
cs:
public class Item : INotifyPropertyChanged
{
// INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string property = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
public double Y { get; set; }
public double Height { get; set; }
double _offset;
public double Offset
{
get { return _offset; }
set
{
_offset = value;
OnPropertyChanged();
OnPropertyChanged(nameof(Text));
}
}
public string Text => $"Y={Y} Height={Height} Offset={Offset}";
}
public partial class MainWindow : Window
{
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < 10; i++)
Items.Add(new Item { Y = i * 40 });
DataContext = this;
}
void Border_SizeChanged(object sender, SizeChangedEventArgs e)
{
var border = (Border)sender;
var current = (Item)border.DataContext;
current.Height = border.ActualHeight;
// recalculate offset
var y = Items[0].Y;
foreach (var item in Items)
{
item.Offset = y > item.Y ? y : item.Y;
y = item.Offset + item.Height;
}
}
}

Related

Hit-Testing in WPF for irregularly-shaped items

I have an irregularly shaped item (a line shape) contained within a ContentControl-derived class ("ShapeItem"). I style it with a custom cursor and I handle mouse-clicks within the ShapeItem class.
Unfortunately WPF thinks that the mouse is "over" my item if it is anywhere within the rectangular bounding box of the ContentControl. That's OK for closed shapes like a rect or circle, but it's a problem for a diagonal line. Consider this image with 3 such shapes on display and their bounding boxes shown in white:
Even if I am in the very bottom left hand corner of the bounding box around the line, it still shows the cursor and the mouse clicks still reach my custom item.
I want to change this so that that the mouse is only considered to be "over" the line detected if I am within a certain distance of it. Like, this region in red (forgive the crude drawing).
My question is, how do I approach this? Do I override some virtual "HitTest" related function on my ShapeItem?
I already know the math to figure out if I'm in the right place. I'm just wondering what approach is the best to choose. What functions do I override? Or what events do I handle, etc. I've gotten lost in the WPF documentation on Hit testing. Is it a matter of overriding HitTestCore or something like that?
Now for code. I host the items in a custom ItemsControl called "ShapesControl".
which uses the custom "ShapeItem" container to to host my view-model objects :
<Canvas x:Name="Scene" HorizontalAlignment="Left" VerticalAlignment="Top">
<gcs:ShapesControl x:Name="ShapesControl" Canvas.Left="0" Canvas.Top="0"
ItemsSource="{Binding Shapes}">
<gcs:ShapesControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" IsItemsHost="True" />
</ItemsPanelTemplate>
</gcs:ShapesControl.ItemsPanel>
<gcs:ShapesControl.ItemTemplate>
<DataTemplate DataType="{x:Type gcs:ShapeVm}">
<Path ClipToBounds="False"
Data="{Binding RelativeGeometry}"
Fill="Transparent"/>
</DataTemplate>
</gcs:ShapesControl.ItemTemplate>
<!-- Style the "ShapeItem" container that the ShapesControl wraps each ShapeVm ine -->
<gcs:ShapesControl.ShapeItemStyle>
<Style TargetType="{x:Type gcs:ShapeItem}"
d:DataContext="{d:DesignInstance {x:Type gcs:ShapeVm}}"
>
<!-- Use a custom cursor -->
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Cursor" Value="SizeAll"/>
<Setter Property="Canvas.Left" Value="{Binding Path=Left, Mode=OneWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Top, Mode=OneWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type gcs:ShapeItem}">
<Grid SnapsToDevicePixels="True" Background="{TemplateBinding Panel.Background}">
<!-- First draw the item (i.e. the ShapeVm) -->
<ContentPresenter x:Name="PART_Shape"
Content="{TemplateBinding ContentControl.Content}"
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentControl.ContentTemplateSelector}"
ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}"
HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
IsHitTestVisible="False"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"
RenderTransformOrigin="{TemplateBinding ContentControl.RenderTransformOrigin}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</gcs:ShapesControl.ShapeItemStyle>
</gcs:ShapesControl>
</Canvas>
My "ShapesControl"
public class ShapesControl : ItemsControl
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is ShapeItem);
}
protected override DependencyObject GetContainerForItemOverride()
{
// Each item we display is wrapped in our own container: ShapeItem
// This override is how we enable that.
// Make sure that the new item gets any ItemTemplate or
// ItemTemplateSelector that might have been set on this ShapesControl.
return new ShapeItem
{
ContentTemplate = this.ItemTemplate,
ContentTemplateSelector = this.ItemTemplateSelector,
};
}
}
And my "ShapeItem"
/// <summary>
/// A ShapeItem is a ContentControl wrapper used by the ShapesControl to
/// manage the underlying ShapeVm. It is like the the item types used by
/// other ItemControls, including ListBox, ItemsControls, etc.
/// </summary>
[TemplatePart(Name="PART_Shape", Type=typeof(ContentPresenter))]
public class ShapeItem : ContentControl
{
private ShapeVm Shape => DataContext as ShapeVm;
static ShapeItem()
{
DefaultStyleKeyProperty.OverrideMetadata
(typeof(ShapeItem),
new FrameworkPropertyMetadata(typeof(ShapeItem)));
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
// Toggle selection when the left mouse button is hit
base.OnMouseLeftButtonDown(e);
ShapeVm.IsSelected = !ShapeVm.IsSelected;
e.Handled = true;
}
internal ShapesControl ParentSelector =>
ItemsControl.ItemsControlFromItemContainer(this) as ShapesControl;
}
The "ShapeVm" is just an abstract base class for my view models. Roughly this:
public abstract class ShapeVm : BaseVm, IShape
{
public virtual Geometry RelativeGeometry { get; }
public bool IsSelected { get; set; }
public double Top { get; set; }
public double Left { get; set; }
public double Width { get; }
public double Height { get; }
}
You could use a ShapeItem class like shown below. It is a Canvas with two Path children, one for hit testing and one for display. It resembles a few of the typical Shape properties (which you may extend according to your needs).
public class ShapeItem : Canvas
{
public ShapeItem()
{
var path = new Path
{
Stroke = Brushes.Transparent,
Fill = Brushes.Transparent
};
path.SetBinding(Path.DataProperty,
new Binding(nameof(Data)) { Source = this });
path.SetBinding(Shape.StrokeThicknessProperty,
new Binding(nameof(HitTestStrokeThickness)) { Source = this });
Children.Add(path);
path = new Path();
path.SetBinding(Path.DataProperty,
new Binding(nameof(Data)) { Source = this });
path.SetBinding(Shape.FillProperty,
new Binding(nameof(Fill)) { Source = this });
path.SetBinding(Shape.StrokeProperty,
new Binding(nameof(Stroke)) { Source = this });
path.SetBinding(Shape.StrokeThicknessProperty,
new Binding(nameof(StrokeThickness)) { Source = this });
Children.Add(path);
}
public static readonly DependencyProperty DataProperty =
Path.DataProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty FillProperty =
Shape.FillProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty StrokeProperty =
Shape.StrokeProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty StrokeThicknessProperty =
Shape.StrokeThicknessProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty HitTestStrokeThicknessProperty =
DependencyProperty.Register(nameof(HitTestStrokeThickness), typeof(double), typeof(ShapeItem));
public Geometry Data
{
get => (Geometry)GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
public Brush Fill
{
get => (Brush)GetValue(FillProperty);
set => SetValue(FillProperty, value);
}
public Brush Stroke
{
get => (Brush)GetValue(StrokeProperty);
set => SetValue(StrokeProperty, value);
}
public double StrokeThickness
{
get => (double)GetValue(StrokeThicknessProperty);
set => SetValue(StrokeThicknessProperty, value);
}
public double HitTestStrokeThickness
{
get => (double)GetValue(HitTestStrokeThicknessProperty);
set => SetValue(HitTestStrokeThicknessProperty, value);
}
}
public class ShapeItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ShapeItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is ShapeItem;
}
}
You would use it an XAML like this:
<gcs:ShapeItemsControl ItemsSource="{Binding Shapes}">
<gcs:ShapeItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</gcs:ShapeItemsControl.ItemsPanel>
<gcs:ShapeItemsControl.ItemContainerStyle>
<Style TargetType="gcs:ShapeItem">
<Setter Property="Data" Value="{Binding RelativeGeometry}"/>
<Setter Property="Fill" Value="AliceBlue"/>
<Setter Property="Stroke" Value="Yellow"/>
<Setter Property="StrokeThickness" Value="3"/>
<Setter Property="HitTestStrokeThickness" Value="15"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</gcs:ShapeItemsControl.ItemContainerStyle>
</gcs:ShapeItemsControl>
However, you may not need a ShapeItem class and a derived ItemsControl at all, when you put the Canvas in the ItemTemplate of a regular ItemsControl:
<ItemsControl ItemsSource="{Binding Shapes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Cursor="Hand">
<Path Data="{Binding RelativeGeometry}" Fill="Transparent"
Stroke="Transparent" StrokeThickness="15"/>
<Path Data="{Binding RelativeGeometry}" Fill="AliceBlue"
Stroke="Yellow" StrokeThickness="3"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you also need to support selection, you should use a ListBox instead of an ItemsControl. A third Path in the ItemTemplate could visualize the selection state.
<ListBox ItemsSource="{Binding Shapes}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Template>
<ControlTemplate TargetType="ListBox">
<ItemsPresenter/>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Canvas Cursor="Hand">
<Path Data="{Binding RelativeGeometry}" Fill="Transparent"
Stroke="Transparent" StrokeThickness="15"/>
<Path Data="{Binding RelativeGeometry}"
Stroke="Green" StrokeThickness="7"
StrokeStartLineCap="Square" StrokeEndLineCap="Square"
Visibility="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListBoxItem},
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Path Data="{Binding RelativeGeometry}" Fill="AliceBlue"
Stroke="Yellow" StrokeThickness="3"/>
</Canvas>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF Datagrid Grouping using a parent object as the expander header

I am using WPF Datagrid to group an observable collection by Parent. I have been following the example here and other examples showing a parent child relationship. So far I have the following:
And I want to get something like this:
Where the Header of a group is just a row as well but still able to collapse/expand child rows. I have tried to make a sub-datagrid without luck. So the underlying type of my collection looks something like this:
public class Task : INotifyPropertyChanged, IEditableObject
{
// memebers
...
public string ProjectName
{
get { return this.m_ProjectName; }
set
{
if (value != this.m_ProjectName)
{
this.m_ProjectName = value;
NotifyPropertyChanged("ProjectName");
}
}
}
public string TaskName
{
get { return this.m_TaskName; }
set
{
if (value != this.m_TaskName)
{
this.m_TaskName = value;
NotifyPropertyChanged("TaskName");
}
}
}
public DateTime DueDate
{
get { return this.m_DueDate; }
set
{
if (value != this.m_DueDate)
{
this.m_DueDate = value;
NotifyPropertyChanged("DueDate");
}
}
}
public bool Complete
{
get { return this.m_Complete; }
set
{
if (value != this.m_Complete)
{
this.m_Complete = value;
NotifyPropertyChanged("Complete");
}
}
}
public Task Parent
{
get { return m_Parent; }
set
{
m_Parent = value;
}
}
So my object type can either by a child or parent, where a child has a reference to its parent. So I am grouping by parent, I just haven't figured out how to make the expandable group headers rows of the same type. Any help is appreciated.
Here is the xaml:
<DataGrid x:Name="dataGrid1"
ItemsSource="{Binding Source={StaticResource cvsTasks}}"
CanUserAddRows="False"
ColumnWidth="*"
RowHeaderWidth="0">
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel HorizontalAlignment="Stretch" >
<TextBlock FontWeight="Bold" Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Margin="5,0,0,0" Width="200" HorizontalAlignment="Stretch"/>
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}" Width="Auto"/>
</DockPanel>
<!--<DataGrid x:Name="dataGrid2"
ItemsSource="{Binding Source={StaticResource vsParentTasks}}"
CanUserAddRows="False"
ColumnWidth="*"
RowHeaderWidth="0"
HeadersVisibility="None" >
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="#FFEEEEEE" />
<Setter Property="Background" Value="#FF112255" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</DataGrid.RowStyle>
</DataGrid>-->
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="White" />
</Style>
</DataGrid.RowStyle>
</DataGrid>

Positioning XAMLUI elements on ItemsControl with Canvas

I'm building a Windows Universal application where I need to put some elements on a canvas. Since I want to do this MVVM style I've read that I should use an ItemsControl for it. Problem is that all the solutions I found are for WPF or Silverlight and don't work for Windows Universal. They appear on the canvas, but they keep positioning themselfs in the left upper corner.
XAML:
<ItemsControl ItemsSource="{Binding MyItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" FontSize="18"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
XAML's code behind:
public MainPage()
{
this.InitializeComponent();
DataContext = new MyItemViewModel();
}
Viewmodel and model:
class MyItemViewModel
{
ObservableCollection<MyItem> _myItems;
public MyItemViewModel()
{
_myItems = new ObservableCollection<MyItem>();
_myItems.Add(new MyItem(10, 10, "First TextBlock"));
_myItems.Add(new MyItem(200, 400, "2nd TextBlock"));
_myItems.Add(new MyItem(400, 200, "The Third TextBlock"));
}
public ObservableCollection<MyItem> MyItems
{
get { return _myItems; }
set { _myItems = value; }
}
}
public class MyItem
{
public MyItem(double left, double top, string text)
{
Left = left;
Top = top;
Text = text;
}
public double Left { get; set; }
public double Top { get; set; }
public string Text { get; set; }
}
Use FrameworkElement as the TargetType instead:
<ItemsControl.ItemContainerStyle>
<Style TargetType="FrameworkElement">
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>

Windows Store Apps: Grid View Group Binding background color?

I have a grid view with a CollectionViewSource as it's items source.
I want to to bind the background property of each group container panel so that each group has its own background color.
how can this be achieved?
I'm trying to use binding in the <GroupStyle.ContainerStyle> of the gridview but can't get it to work.
Since the list will be grouped already, then applying a background on each GridViewItem will do the trick, depending on whether you want to define the backgound in each item as a property or use a converter to do that :
public class Data
{
public String Prop1 { get; set; }
public String Prop2 { get; set; }
public SolidColorBrush GroupeBrush { get; set; } //the groupe background color
}
And the xaml,
<Page.Resources>
<CollectionViewSource x:Name="DataCollection" IsSourceGrouped="true" />
</Page.Resources>
<Grid>
<GridView SelectionMode="None" ItemsSource="{Binding Source={StaticResource DataCollection}}" >
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
<Setter Property="VerticalContentAlignment" Value="Stretch"></Setter>
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate>
<Grid Background="{Binding GroupeBrush}">
<TextBlock Text="{Binding Prop2}" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
Or you could as well play around the GridView GroupStyle although you will need to find a way to bind the background from the Style Setter :
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border Background="Black" HorizontalAlignment="Stretch">
<TextBlock Text='{Binding Key}' Foreground="White" Margin="5" Style="{StaticResource SubheaderTextBlockStyle}" />
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="MinWidth" Value="600"/>
<Setter Property="BorderBrush" Value="DarkGray"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="Margin" Value="3,0"/>
<Setter Property="Background" Value="BurlyWood"/>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</GridView.GroupStyle>
Here the entire code behind in case any one wants to experiment more
public sealed partial class MainPage : Page
{
private ObservableCollection<Data> _datas = new ObservableCollection<Data>()
{
new Data()
{
Prop1 = "val1",
Prop2 = "val2",
GroupeBrush=new SolidColorBrush(Colors.Blue)
}, new Data()
{
Prop1 = "val1",
Prop2 = "val2",
GroupeBrush=new SolidColorBrush(Colors.Blue)
}, new Data()
{
Prop1 = "val1",
Prop2 = "val3",
GroupeBrush=new SolidColorBrush(Colors.Blue)
}, new Data()
{
Prop1 = "val2",
Prop2 = "val4",
GroupeBrush=new SolidColorBrush(Colors.Green)
}, new Data()
{
Prop1 = "val3",
Prop2 = "val5",
GroupeBrush=new SolidColorBrush(Colors.Red)
},
};
public ObservableCollection<Data> Datas
{
get
{
return _datas;
}
set
{
if (_datas == value)
{
return;
}
_datas = value;
}
}
public MainPage()
{
this.DataContext = this;
InitializeComponent();
DataCollection.Source = GetAllGrouped();
}
public IEnumerable<IGrouping<string, Data>> GetAllGrouped()
{
return Datas.GroupBy(x => x.Prop1);
}
}
public class Data
{
public String Prop1 { get; set; }
public String Prop2 { get; set; }
public SolidColorBrush GroupeBrush { get; set; } //the groupe background color
}
OK, here's what I've got. I had to modify the template of the Group Container Style:
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Margin" Value="10,0,0,0" />
<Setter Property="Padding" Value="20"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<Border Background="{Binding Group, Converter={StaticResource ThemeColorConverter}}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl x:Name="HeaderContent"
Content="{TemplateBinding Content}"
ContentTransitions="{TemplateBinding ContentTransitions}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"
Margin="{TemplateBinding Padding}"
TabIndex="0"
IsTabStop="False" />
<ItemsControl x:Name="ItemsControl"
Grid.Row="1"
ItemsSource="{Binding GroupItems}"
IsTabStop="False"
TabNavigation="Once"
TabIndex="1" >
<ItemsControl.ItemContainerTransitions>
<TransitionCollection>
<AddDeleteThemeTransition />
<ContentThemeTransition />
<ReorderThemeTransition />
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</ItemsControl.ItemContainerTransitions>
</ItemsControl>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
This line did the trick:
<Border Background="{Binding Group, Converter={StaticResource ColorConverter}}"/>
Binding to Group gives you access to the group's data source.

unable to get, set selectedItem from the listbox containing radiobuttons being generated dynamically in wpf MVVM

Am trying to populate listbox with dynamic radiobuttons which are being customized to togglebuttons. I could populate listbox items with radiobuttons as said above. However, once we select any of the radiobuttons am unable to set the selected item from list of radiobuttons in my viewmodel object while debugging.
The following is the xaml code in my resource directory
<Style x:Key="ScreensList" TargetType="{x:Type ListBox}">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Margin" Value="2, 2, 2, 0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent">
<RadioButton
VerticalAlignment="Center" GroupName="{Binding RelativeSource={RelativeSource TemplatedParent}}"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}">
<RadioButton.Template>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<StackPanel Width="80" Height="60" Orientation="Vertical" HorizontalAlignment="Left" Margin="10,10,20,10">
<Image Source="Default.png" Height="40"></Image>
<TextBlock Text="{Binding Path=ScreenNumber}" FontSize="11"></TextBlock>
</StackPanel>
</ToggleButton>
</StackPanel>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
the below is my xaml code in xaml page
<ListBox x:Name="ScreensList" ItemsSource="{Binding ScreenCollection}"
SelectedItem="{Binding Path=ScreenManager}"
Style="{StaticResource ScreensList}" Width="365">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel FlowDirection="LeftToRight" Orientation="Horizontal" >
</WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
the following is the viewmodel.cs
public ObservableCollection<ScreensManager> ScreenCollection { get; set; }
private ScreensManager _screenManager;
public ScreensManager ScreenManager
{
get { return _screenManager; }
set
{
if (_screenManager != value)
{
if (_screenManager != null)
{
_screenManager = value;
}
}
}
}
private void AddScreens()
{
int screenCount = Screen.AllScreens.Length;
if (ScreenCollection == null)
ScreenCollection = new ObservableCollection<ScreensManager>();
for (int screenCounter = 1; screenCounter <= screenCount; screenCounter++)
{
if (screenCounter == 1)
{
_screenManager = new ScreensManager();
_screenManager.ScreenNumber = screenCounter;
ScreenCollection.Add(_screenManager);
}
}
}
the following is the code in my ScreenManager.cs model class file
public ScreensManager()
{
}
private int _screenNumber;
public int ScreenNumber
{
get { return _screenNumber; }
set
{
_screenNumber = value;
OnPropertyChanged("ScreenNumber");
}
}
private bool _selectedScreen;
public bool SelectedScreen
{
get { return _selectedScreen; }
set
{
if (_selectedScreen = value)
{
_selectedScreen = value;
if (_selectedScreen != value)
{
OnPropertyChanged("SelectedScreen");
}
}
}
}
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
Am unable to find where I am actually going wrong as completely new to mvvm, someone please help me resolve my issue..Thanks in advance.
Your SelectedScreen is of type bool, and should be of type ScreensManager
Your ListBox.ItemsSource is bound to an ObservableCollection<ScreensManager>, meaning your ListBox contains a collection of ScreensManager objects, however your SelectedItem is of type bool. A bool object is never equal to a ScreensManager object, so WPF doesn't select anything since the SelectedItem is not found in the ItemsSource.
Change the SelectedScreen type to be ScreensManager instead of bool, and be sure it is equal to an item that exists in the ScreenCollection. WPF compares objects by reference, not value, so
ScreenManager.SelectedScreen = ScreenCollection.FirstOrDefault(); // Works
ScreenManager.SelectedScreen = new ScreensManager() { ... }; // Won't work
Make sure the binding is in two-way mode:
SelectedItem="{Binding Path=ScreenManager.SelectedScreen, Mode=TwoWay}"

Categories