Which Control will be more appropriate to the output shown below? - c#

My project was working good before I made some changes in my database and my code.
Before Changes :
Output :
Tile1 Tile7 .......... Tile(N-x)
Tile2 Tile8 Tile(N-x+1)
Tile3 Tile9 ....
Tile4 Tile10 ....
Tile5 Tile11 ....
Tile6 Tile12 Tile(N)
Table in Database: 1------- [Primary Key]
Title |
Background |
Image |
ParentID *------- [Foreign Key]
XAML :
<ListBox Grid.Row="1" x:Name="lst"
ItemsSource="{Binding ChildrenMenus}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Vertical" MaxHeight="{Binding ElementName=lst, Path=ActualHeight}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Width" Value="250" />
<Setter Property="Height" Value="125" />
<Setter Property="Margin" Value="2.5" />
<Setter Property="Padding" Value="2.5" />
<Setter Property="Background" Value="{Binding Background, Converter={StaticResource stringToBrushConverter}}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="VerticalContentAlignment" Value="Bottom" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="{Binding Background, Converter ={StaticResource stringToBrushConverter}}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="125" Width="250">
<Path Data="{Binding Image}" VerticalAlignment="Center"
Stretch="Uniform" Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
Width="68" Height="68" Margin="10" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<TransformGroup.Children>
<RotateTransform Angle="0" />
<ScaleTransform ScaleX="1" ScaleY="1" />
</TransformGroup.Children>
</TransformGroup>
</Path.RenderTransform>
</Path>
<TextBlock Text="{Binding Title, Converter={StaticResource spaceToNewLineConverter}}" VerticalAlignment="Top"
Margin="40,10,10,10" FontSize="24" Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Currently:
Required Output:
Text1 Text2 Text3 .......... Text(N)
Tile1 Tile3 Tile7 Tile9 Tile13 Tile(N-x) .....
Tile2 Tile4 Tile8 Tile10 Tile(N-x + 1) .....
Tile5 Tile11 .... .....
Tile6 Tile12 .... Tile(N)
Changes in database:
I have tried many changes in ViewModel and XAML files and now it got messed up. So, if I post those codes then also it will not be useful to anybody.
I hope I have mentioned everything correctly in question.
Update
First of all I am sorry. My internet connection was down for the whole day. I have read your messages just now.
Now, I have got something. I can get data from database in Design_Master_MenuItems. See the Image below:
But still Binding does not work correctly. I mean my ListBoxes inside ItemsControl are not being populated.
Here is my current XAML:
<ItemsControl ItemsSource="{Binding MenuCategories}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Title}" FontSize="30" />
<ListBox Grid.Row="1" x:Name="lst"
ItemsSource="{Binding Design_Master_TileItems}" DisplayMemberPath="Title">
</ListBox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is myViewModel :
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
using (Entities db = new Entities())
{
ParentMenus = new ObservableCollection<Design_Master_ParentMenus>(from d in db.Design_Master_ParentMenus select d);
if (SelectedParent != null)
MenuCategories = new ObservableCollection<Design_Master_Categories>(from d in db.Design_Master_Categories
where d.ParentMenuID == SelectedParent.ParentMenuID
select d);
}
}
private ObservableCollection<Design_Master_ParentMenus> _parentMenus;
public ObservableCollection<Design_Master_ParentMenus> ParentMenus
{
get
{
return _parentMenus;
}
set
{
_parentMenus = value;
OnPropertyChanged("ParentMenus");
}
}
private Design_Master_ParentMenus _selectedParent;
public Design_Master_ParentMenus SelectedParent
{
get
{
return _selectedParent;
}
set
{
_selectedParent = value;
OnPropertyChanged("SelectedParent");
using (Entities db = new Entities())
{
MenuCategories = new ObservableCollection<Design_Master_Categories>(from d in db.Design_Master_Categories
where d.ParentMenuID == SelectedParent.ParentMenuID
select d);
}
}
}
private ObservableCollection<Design_Master_Categories> _menuCategories;
public ObservableCollection<Design_Master_Categories> MenuCategories
{
get
{
return _menuCategories;
}
set
{
_menuCategories = value;
OnPropertyChanged("MenuCategories");
}
}
}
Yes, and I will not be available for next 10 hours. If you find any mistake in the above code you may comment. Thanks for a big helping hand.
Update2
Yes now I find the binding error in Output window:
System.Windows.Data Error: 17 : Cannot get 'Design_Master_TileItem' value (type 'ICollection`1') from ''
(type 'Design_Master_Catego_79D2EFE4D31EC6575261E40C340C9D078D37C022F94C70A5F8A88A9017957C24').
BindingExpression:Path=Design_Master_TileItem;
DataItem='Design_Master_Catego_79D2EFE4D31EC6575261E40C340C9D078D37C022F94C70A5F8A88A9017957C24'
(HashCode=28842409); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type
'IEnumerable') TargetInvocationException:'System.Reflection.TargetInvocationException: Property accessor
'Design_Master_TileItem' on object
'System.Data.Entity.DynamicProxies.Design_Master_Catego_79D2EFE4D31EC6575261E40C340C9D078D37C022F94C70A5F8A8
8A9017957C24' threw the following exception:'The ObjectContext instance has been disposed and can no
longer be used for operations that require a connection.' ---> System.ObjectDisposedException: The
ObjectContext instance has been disposed and can no longer be used for operations that require a
connection.
System.Windows.Data Error: 17 : Cannot get 'Design_Master_TileItem' value (type 'ICollection`1') from ''
(type 'Design_Master_Catego_79D2EFE4D31EC6575261E40C340C9D078D37C022F94C70A5F8A88A9017957C24').
BindingExpression:Path=Design_Master_TileItem;
DataItem='Design_Master_Catego_79D2EFE4D31EC6575261E40C340C9D078D37C022F94C70A5F8A88A9017957C24'
(HashCode=13006057); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type
'IEnumerable') TargetInvocationException:'System.Reflection.TargetInvocationException: Property accessor
'Design_Master_TileItem' on object
'System.Data.Entity.DynamicProxies.Design_Master_Catego_79D2EFE4D31EC6575261E40C340C9D078D37C022F94C70A5F8A8
8A9017957C24' threw the following exception:'The ObjectContext instance has been disposed and can no
longer be used for operations that require a connection.' ---> System.ObjectDisposedException: The
ObjectContext instance has been disposed and can no longer be used for operations that require a
connection.

First you want a ListView with a horizontal StackPanel as the panel template to get your "big" blocks.
Then, for each block, you'll need a "header" and then another ListView, this time with a vertical WrapPanel as the panel template. Below is a "shell" example that would need some styling and bindings to get it to look exactly the way you want, but hopefully it gets you on the right track.
<ListView>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock/>
<ListView>
<ListView.ItemPanelTemplate>
<WrapPanel Orientation="Vertical"></WrapPanel>
</ListView.ItemPanelTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView>
Update:
To have only "one selection", make sure that selecting either list box calls the setter on your property. I normally don't do this with RelativeSource, so here is an example if you want to try it (where your window/user control is named "Root":
"{Binding ElemantName=Root, Path=DataContext.SelectedTileItem}"
A converter is going to get really complicated to do this. This answer has an accepted way of setting up what you are trying to do, and it is probably the way you want to go (I would use the group name route, since that is basically what you are trying to do).

Related

Update datagrid's GroupHeader when item property changes

I'm having some difficulty updating a certain binding.
I have a class DeviceList that loads some devices, it inherits from ObservableCollection and is listed as a resource in my XAML:
<local:DeviceList x:Key="Devices" />
Then, I have a CollectionViewSource that uses this devicelist as source, and groups it by a property from the Device:
<CollectionViewSource x:Key="cvsDevices" Source="{StaticResource Devices}" Filter="CollectionViewSource_Filter">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="GroupId" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
A Datagrid binding to this CVS, which has a group header style:
<DataGrid x:Name="dataGrid" ItemsSource="{Binding Source={StaticResource cvsDevices}}">
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
bla bla
</DataGrid.Columns>
</DataGrid>
And then finally the Group Header style in the resources:
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="White" Foreground="Black">
<Expander.Header>
<StackPanel Orientation="Horizontal" Height="30">
<Border Margin="5" Width="20" Height="20" Background="{Binding Path=Items, Converter={StaticResource DeviceGroupToColorConverter}}" CornerRadius="10" />
<TextBlock VerticalAlignment="Center" Padding="3" Text="{Binding Name, Converter={StaticResource DeviceGroupToGroupTitleConverter}}" />
<TextBlock VerticalAlignment="Center" Padding="3" Text="{Binding ItemCount, Converter={StaticResource ItemCountToStringConverter}}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see, there's a Border there that binds to "Items". This is a property of "CollectionViewGroup": https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.collectionviewgroup?view=netcore-3.1
Basically each of my devices has a property "Connection", and when this property changes, I would like to set the color of this border in the corresponding group header.
The binding works fine the first time, but after that the DeviceGroupToColorConverter isn't called anymore when a connection changes. Device implements INotifyPropertyChanged, but I have no idea how to propagate that event to CollectionViewGroup's Items property. In fact, I have no idea where CollectionViewGroup instances live. I only have access to the CollectionViewSource.
I would like to avoid refreshing the entire DataGrid. I've read that it resets my expanders and also, why refresh the entire datagrid when only a certain group's header should change?
I have solved it by changing the binding to a MultiBinding and adding a binding with a Source set to the DeviceList:
<Border Margin="5" Width="20" Height="20" CornerRadius="10">
<Border.Background>
<MultiBinding Converter="{StaticResource DeviceGroupToColorConverter}">
<Binding Source="{StaticResource Devices}" Path="Devices" />
<Binding Path="Name" />
</MultiBinding>
</Border.Background>
</Border>
the "Devices" property of the DeviceList class is a simple getter that returns "this":
public ObservableCollection<Device> Devices
{
get
{
return this;
}
}
I let the DeviceList listen to any property changes on device, and invoke PropertyChanged on the "Devices" property of DeviceList to pass on this event to the MultiBinding.
I then use the Name binding in the MultiBinding to filter my devices based on the group that they're in. Now I don't need to refresh the whole grid and my performance is good.

How do I select a new ListBoxItem in C# WPF after I just inserted it automatically

I have the following problem with my calculator app which I'm doing in the MVVM pattern.
I'm redoing the Windows 10 Calculator in Standard Mode. I made an ObservableCollection of MemoryItem.
MemoryItem is a class that contains an int for the Index, a double for the value and a RelayCommand for the MemoryButtons.
Basically it looks like this and is connected to my ViewModel:
public class MemoryItem
{
public double MemoryItemValue { get; set; }
public int SelectedMemoryItemIndex { get; set; }
public RelayCommand MemoryItemChange { get; set; }
}
So I've binded the SelectedMemoryItemIndex Property to the SelectedItemIndex in WPF.
My ListBox looks like this:
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource MemoryListBoxStyle}"
Visibility="{Binding MemoryVisibility}" ItemsSource="{Binding MemoryCollection}"
SelectedItem="{Binding SelectedMemoryItem}" SelectionMode="Extended" SelectedIndex="{Binding SelectedMemoryItemIndex}"
HorizontalContentAlignment="Right"/>
While the style of it looks like this:
<Style x:Key="MemoryListBoxStyle" TargetType="ListBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<UniformGrid Rows="2" Margin="5">
<TextBlock Style="{StaticResource DisplayStyle}" Text="{Binding MemoryItemValue}" FontSize="20"/>
<DockPanel LastChildFill="False">
<Button Content="MC" Style="{StaticResource MemoryItemButton}"
Command="{Binding MemoryItemChange}" CommandParameter="{x:Static md:MemoryUsage.Clear}"/>
<Button Content="M+" Style="{StaticResource MemoryItemButton}"
Command="{Binding MemoryItemChange}" CommandParameter="{x:Static md:MemoryUsage.Add}"/>
<Button Content="M-" Style="{StaticResource MemoryItemButton}"
Command="{Binding MemoryItemChange}" CommandParameter="{x:Static md:MemoryUsage.Substract}"/>
</DockPanel>
</UniformGrid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The bindings work BUT I don't know how to have the new MemoryItem selected after Inserting the new MemoryItem and deleting the new one. Is there a better of way inserting the new item? ObservableCollection doesn't include a method to update a specific item (as far as I know).
This is the method I'm using to add the value to the MemoryItemValue and insert it in my Collection:
case MemoryUsage.Add:
if (SelectedMemoryItemIndex == -1)
{
SelectedMemoryItemIndex = 0;
}
MemoryItemValue += Eingabe1;
MemoryCollection.Insert(SelectedMemoryItemIndex +1, MItem);
MemoryCollection.RemoveAt(SelectedMemoryItemIndex);
break;
This way it worked but I always have to select the new inserted MemoryItem.
I'm thankful for ANY help provided by you.
Please keep in mind that I'm a beginner in programming and this is my first SO question ever.
Here is a post that helps answer this question.
But basically:
Create an IsSelected property on your MemoryItem class and bind ListBoxItem.IsSelected to that property.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
When you want your new item selected, just set IsSelected to true.
IsSelected = true;
And shazam! It should work.
Here is code copied from another answer that may give you more information.
<ListBox ItemsSource="{Binding Items, Source={StaticResource ViewModel}}"
SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsItemSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemText}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Forgive me for leaving that example exactly as I found it.

How to move an Ellipse from ViewModel

I'm creating a WPF application where I have to bind a collection to a Canvas, then, each element is showed as an ellipse. So, since I want to be able to move all the elements on demand I bound each element's viewmodel to Canvas.Left and Canvas.Top. However, when I assign a new value to my viewmodel's bound properties the ellipse does not move.
My xaml file:
<ItemsControl ItemsSource="{Binding Elements}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Viewbox >
<Grid>
<Ellipse Stroke="Black"
Width="{Binding ShapeWidth, Mode=TwoWay}"
Height="{Binding ShapeHeight, Mode=TwoWay}"
Canvas.Left="{Binding ShapeCanvasLeft, Mode=TwoWay}"
Canvas.Top="{Binding ShapeCanvasTop, Mode=TwoWay}"
Fill="{Binding Background}"
/>
<TextBlock Text="{Binding ElementName}"
HorizontalAlignment="Center"
TextAlignment="Center"
VerticalAlignment="Center"
/>
</Grid>
</Viewbox>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
My viewmodel file:
public double ShapeCanvasLeft
{
get
{
return m_shapeCanvasLeft;
}
set
{
m_shapeCanvasLeft = value;
OnPropertyChanged(nameof(ShapeCanvasLeft));
}
}
public double ShapeCanvasTop
{
get
{
return m_shapeCanvasTop;
}
set
{
m_shapeCanvasTop = value;
OnPropertyChanged(nameof(ShapeCanvasTop));
}
}
. . .
Then in some other place:
ShapeCanvasTop = 100; // This does not work
So, what am I doing wrong? The shape does not move, however, the properties ShapeHeight and ShapeWidth work perfectly, i.e. the shape resizes correctly.
Thanks in advance.
ItemsControl items get wrapped in a parent container, that's what you need to be setting the position of:
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding ShapeCanvasLeft}" />
<Setter Property="Canvas.Top" Value="{Binding ShapeCanvasTop}" />
</Style>
</ItemsControl.ItemContainerStyle>

Variable background color in an itemscontrol

I would like to create an items control that alternates the background color of an item based on the alternation index of the group it belongs to. In reference to the classes I list below, I would like it to, for example, have the background of the first three RandomHouse books as black, and then when it encounters the next Publisher, the background changes back to white, and so on and so forth for as many unique publishers there are. The number and name of the publishers is nondeterministic and is only evaluated at runtime. I have tried to do it with xaml to the best of my ability but it doesnt seem the alternationindex can be accessed for a GroupItem for whatever reason. Any help would be appreciated.
class Book
{
String Publisher {get; set;}
String Title {get; set;}
}
class ViewModel
{
var listBooks = new ObservableCollection<Book>();
listBooks.Add(new Book(){Publisher = "RandomHouse", Title = "Title1"});
listBooks.Add(new Book(){Publisher = "RandomHouse", Title = "Title2"});
listBooks.Add(new Book(){Publisher = "Penguin", Title = "Title5"});
ObservableCollection<Book> ListBookItems {get {return listBooks.Orderby(e => e.Publisher).ToList(); } }
}
<UserControl.Resources>
<Style TargetType="ItemsControl" x:Key="ListBookStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ScrollViewer CanContentScroll="True">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontFamily">
<Setter.Value>Consolas</Setter.Value>
</Setter>
</Style>
<DataTemplate DataType="{x:Type models:Book}">
<Grid IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Publisher" Width="100"/>
<ColumnDefinition SharedSizeGroup="Title" Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock
HorizontalAlignment="Left"
Text="{Binding Publisher}"
Grid.Column="0"
FontWeight="Bold"
Margin="5"/>
<TextBlock
HorizontalAlignment="Left"
Text="{Binding Title}"
Grid.Column="1"
FontWeight="Bold"
Margin="5"
/>
</Grid>
</DataTemplate>
<CollectionViewSource x:Key="ListBookItems" Source="{Binding ListBookItems}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Publisher"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<DockPanel>
<ItemsControl
ItemsSource="{Binding Source={StaticResource ListBookItems}}"
Style="{StaticResource ListBookStyle}">
<ItemsControl.GroupStyle>
<GroupStyle AlternationCount="2">
<GroupStyle.ContainerStyle >
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Foreground" Value="#FF444444"/>
<Setter Property="Background" Value="#FF000000"/>
<!--<Style.Triggers>
<Trigger Property="AlternationIndex" Value="0">
<Setter Property="Foreground" Value="#FF444444"/>
<Setter Property="Background" Value="#FFD9D9D9"/>
</Trigger>
<Trigger Property="AlternationIndex" Value="1">
<Setter Property="Foreground" Value="#FF444444"/>
<Setter Property="Background" Value="#FFEFEFEF"/>
</Trigger>
</Style.Triggers>-->
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer CanContentScroll="True">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DockPanel>
You are very close. You just need to specify ItemsControl.AlternationIndex as the property name in each trigger, instead of just AlternationIndex.
That said, frankly I had second-thoughts about even proposing this as an answer, except that I can't actually get that to work. That is, while I can see in the debugger that it correctly sets the GroupItem.Background property value as desired, I see no visible effect on-screen. It's as if the items-presenter for the group is ignoring the GroupItem's background property.
The reason I'm going ahead and posting this as an answer even though it doesn't completely address the issue is that getting the GroupItem.Background property value to be respected by the actual presenter in the GroupItem is a completely different problem than correctly using the AlternationIndex value. So while you can use this answer to get the AlternationIndex value to be correctly bound, you will need to do some more work and/or post another question to dig into why having correctly set the Background property value, that does not actually change the background as shown on the screen.
I wish I could also explain how to get the background property in GroupItem to affect the display on the screen. I hope that if you figure that out, you will at least follow up with a comment here, explaining the answer. :) (Or if you post a question and someone else explains it, comment with a reference to that answer).

Drawing a border only on the last item inside my listview

I'm trying to create a control like this:
The control denotes the status completion of 4 tile objects (in the above attached pic 2 is completed and 2 is not)
For achieving this I have created a ListView with horizontal orientation, and I'm creating a datatemplate which has some borders inside.Not sure whether this is the right approach or whether a simpler approach is possible, however the issue I'm facing is the following:
I'm not able to close the border of the last item..this is because I have 4 items inside my itemsource of the listview and each item draws a border to it's left. Question is how do I specifically draw the right border for the last item.
The line which passess through the middle should actually go behind the gray shading..how do I achieve that?
Code is as below:
<Style x:Key="FishBoneStyle" TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Completed}" Value="True">
<Setter Property="Background" Value="DarkGray"/>
<Setter Property="Margin" Value="0,3,-2,3"/>
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="FishBoneTemplate">
<Grid Height="25">
<Border BorderThickness="1,0,0,0" BorderBrush="Black" Height="25" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
<Border Style="{StaticResource FishBoneStyle}" x:Name="FishBoneBorder" Width="25">
</Border>
</Grid>
</DataTemplate>
<!-- The Main Grid-->
<Grid Grid.Row="1" Height="30" Width="130" HorizontalAlignment="Left">
<ListView BorderThickness="0,0,0,0" BorderBrush="Black" ItemsSource="{Binding ProgressTiles}" ItemTemplate="{StaticResource FishBoneTemplate}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<!-- The line which passess through the center-->
<Border VerticalAlignment="Center" HorizontalAlignment="Left" Width="100" Margin="3,0,2,0" BorderBrush="Black" BorderThickness="1"></Border>
</Grid>
..and here is the model which is lying inside an observable collection in my viewmodel.There will be always 4 of them.
public class ProgressTile : INotifyPropertyChanged
{
private bool _completed;
public bool Completed
{
get { return _completed; }
set
{
_completed = value;
InvokePropertyChanged("Completed");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(string e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(e));
}
}
Please can you suggest how to get a look consistent to the attached picture. Also can you please suggest how to solve the issue with drawing a border for the last item and sending the line passing through the middle to background?
Can be done much simpler:
<Window x:Class="So17362172FishBoneProgressBar.MainWindow" x:Name="root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Fish Bone Progress Bar" Height="350" Width="525" SnapsToDevicePixels="True">
<Grid Width="100" Height="20">
<TickBar Fill="Gray" TickFrequency="25" Placement="Top"/>
<TickBar Fill="Gray" TickFrequency="25" Placement="Bottom"/>
<Line Stroke="Gray" X1="0" Y1="10" X2="100" Y2="10"/>
<ItemsControl ItemsSource="{Binding Tiles, ElementName=root}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle x:Name="rect" Fill="Gray" VerticalAlignment="Center" Width="25" Height="10"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Completed}" Value="False">
<Setter TargetName="rect" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
ListView is unnecessary. Much simpler ItemsControl is enough for this.
Ticks can be drawn with TickBar. As inner ticks are drawn smaller, two tick bars are put over each other (it shouldn't be an issue, as these are very lightweight).
Order of drawing is determined by order in the logical tree. If you want something to be drawn over another, you put it into XAML lower.
Grid is unnecessary if it contains only one control.

Categories