Interactive Grid WPF - c#

I am currently working in C# WPF 4.5 and am attempting to create an interactive grid. Given the grid cell square size (width = height), number of columns and number of rows, I'd like to dynamically generate the grid on the screen.
For the generated grid, I'd like the grid lines to be visible. I'm thinking this could be emulated by just drawing a border within each cell if nothing else. On top of all this, I need an event to be able to determine what cell the user clicks on (i.e., on click return gird column and row) with the ability to select one or more cell at a time.
Any help or ideas on how I could create something like this or is there a better way to go about it? Thanks in advance!
Edit:
So here's some progress I've made:
Created a custom checkbox:
<Style x:Key="styleCustomCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid x:Name="Background" Background="Transparent">
<Rectangle x:Name="fillCheckBox"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="fillCheckBox" Property="Fill" Value="Transparent"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="fillCheckBox" Property="Fill" Value="Red"/>
<Setter TargetName="fillCheckBox" Property="Opacity" Value=".3"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And then just populate a grid with the custom checkboxes of that style:
<CheckBox Grid.Column="1" Grid.Row="1" Style="{StaticResource styleCustomCheckBox}" />
So now I'd like to map each of checkboxes within the grid where I can gather whether the checkbox is checked or not and what grid row and column it is in, how would I go about this?

I would use an ItemsControl with its ItemsPanelTemplate set to a UniformGrid, and it's ItemTemplate set to your CheckBox
It would be easiest if you could bind your ItemsControl.ItemsSource to a collection of data objects in your code behind. For example:
<ItemsControl ItemsSource="{Binding MyCollection}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="5" Columns="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ItemTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<CheckBox Style="{StaticResource styleCustomCheckBox}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you needed to specify the Row or Column index of the checked item, you could add that to your data item and use a regular Grid instead of a UniformGrid, and apply an ItemContainerStyle like this:
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}" />
<Setter Property="Grid.Row" Value="{Binding RowIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
To determine the item clicked on, that depends on how you structure your data object and what you actually want to do in your click.
If you wanted the data item behind the square, you could just use the DataContext, such as
void MyCheckBox_Clicked(object sender, EventArgs e)
{
var selectedItem = ((CheckBox)sender).DataContext;
}
Or if you had something like a data item with an IsChecked property like this:
<CheckBox IsChecked="{Binding IsChecked}"
Style="{StaticResource styleCustomCheckBox}" />
You could easily attach an event to the PropertyChanged event for the data item's IsChecked property, and run some code there.
As a third alternative you could use a Button for your ItemTemplate and pass the selected item as the CommandParameter
<Border BorderBrush="Red" BorderThickness="1">
<Button CommandParameter="{Binding }" ... />
</Border>
I can't give you an definite answer because I don't know your code base and what you're trying to do, but I hope this gives you enough information to give you a rough start and point you in the right direction for your situation.

Related

Is there a way to prevent or recover from ListViewItems becoming MS.Internal.NamedObject

I have a UserControl that is a ListView with a Canvas as the ItemsPanel. The ItemsSource of the ListView is bound to a CollectionViewSource with it's Source property bound to an ObservableCollection< DeviceItem >. DeviceItem is a custom class that, among other things, holds the X/Y coordinates of the ListViewItem/DeviceItem which gets serialized when the user wants to save their work. On PreviewMouseDown and PreviewTouchDown I check the ListViewItem that's being clicked/touched to make sure that the content of the ListViewItem is a DeviceItem so that as the user moved it around the screen the software can updates the coordinates on the DeviceItem. This works 99% of the time but I run into a problem (sometimes) after the user removes items from the collection and then adds new items. After removing/adding, when the user clicks/touches the ListViewItem it's Content is typeof MS.Internal.NamedObject and in that case I can't act on the item. I'm sure it's worth mentioning that the user is adding/removing from a separate control in another window that is bound to the same collection in our data model (ObservableCollection< DeviceItem >).
Currently the user has to close and reopen the software when this happens. I'm trying to find a way to recover without restarting the software.
<UserControl.Resources>
<Style x:Key="CanvasStyle" TargetType="{x:Type Canvas}">
<EventSetter Event="Loaded" Handler="Canvas_Loaded"/>
</Style>
<CollectionViewSource Source="{Binding DiagramCollection}" x:Key="radioPacksDataView" />
</UserControl.Resources>
<Grid>
<ListView Name="listBoxDevices"
Background="Transparent"
BorderThickness="0"
Stylus.IsPressAndHoldEnabled ="False"
ItemsSource="{Binding Source={StaticResource radioPacksDataView}}"
PreviewMouseMove="ListBox_PreviewMouseMove"
PreviewMouseDown="ListBox_PreviewMouseDown"
PreviewMouseUp="ListBox_PreviewMouseUp"
PreviewTouchDown="ListBox_PreviewTouchDown"
PreviewTouchMove="ListBox_PreviewTouchMove"
PreviewTouchUp="ListBox_PreviewTouchUp"
SnapsToDevicePixels="True"
IsHitTestVisible="{Binding AccessRights, ConverterParameter=Unlocked, Converter={StaticResource enumBooleanConverter}, Source={x:Static appData:Globals.CurrentUser}}"
VirtualizingPanel.IsVirtualizing="False"
IsSynchronizedWithCurrentItem="False">
<ListView.Style>
<Style>
<Setter Property="ItemsControl.ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas Style="{DynamicResource CanvasStyle}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=window, Path=IconStyle}" Value="0">
<Setter Property="ListView.ItemContainerStyle" Value="{DynamicResource MultiDeviceItemContainerStyle}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=window, Path=IconStyle}" Value="1">
<Setter Property="ListView.ItemContainerStyle" Value="{DynamicResource MultiDeviceItemContainerStyleV2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
<ListView.LayoutTransform>
<ScaleTransform CenterX="0"
CenterY="0"
ScaleX="{Binding ZoomScale}"
ScaleY="{Binding ZoomScale}"/>
</ListView.LayoutTransform>
</ListView>
</Grid>
And then in code behind
private void ListBox_PreviewMouseDown(object sender, MouseEventArgs e)
{
if (m_selectedListViewItem == null)
{
m_selectedListViewItem = ItemsControl.ContainerFromElement((ListBox)sender, e.OriginalSource as DependencyObject) as ListViewItem;
//this is the section that fails after removing/adding items, object is typeof(MS.Internal.NamedObject)
if (m_selectedListViewItem.Content.GetType().IsSubclassOf(typeof(DeviceItem)))
{
m_selectedDeviceItem = m_selectedListViewItem.Content as DeviceItem;
}
}
}

Making item selected of a ListView of ToggleButtons

I have a ListView and each item has a toggle button. I want the to be able to toggle the button when the item is selected from the list, and untoggle when the item is deselected. It has to follow mvvm, so no code behind.
Here is my setup:
<ListView x:Name="stampList"
ItemsSource="{Binding AllStampImages}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="0,2,0,0">
<ToggleButton Width="72"
Height="72"
Command="{Binding StampSelectedCommand}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My problem is, when I hit the toggle button the item is not selected. Likewise, when I hit outside the toggle button (still within the boundaries of the listView item), the item is selected but the button is not toggled.
How do I tie the two together?
You might have to bind the Selector.IsSelected property to you custome property. In this case to property of you toggle button.
Try something like:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Selector.IsSelected" Value="{Binding [Value of your toggle button], Mode=TwoWay}" />
</Style>
</ListView.ItemContainerStyle>
The cleanest way to do that is to have a collection of items provided by ViewModel or Model layer.
Each item should have a IsSelected boolean property :
class LineItem
{
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set { isSelected = value; }
}
private String label;
public String Label
{
get { return label; }
set { label = value; }
}
}
There should be some binding to the model IsSelected property :
2.1 On the ListView.ItemTemplate
<ListView x:Name="list1" SelectionMode="Multiple" ItemContainerStyle="{DynamicResource ListViewItemStyle1}" Margin="0,0,334,0">
<ListView.ItemTemplate>
<DataTemplate>
<ToggleButton Margin="5" Content="{Binding Label}" IsChecked="{Binding IsSelected}"/>
</DataTemplate>
</ListView.ItemTemplate>
2.2 On the ListViewItem Template
Create a Template with right clicking on the listview,
In the menu : "Edit additional templates/Edit generated Item container (Empty).
Fill with following code :"
<Style x:Key="ListViewItemStyle1" TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Grid x:Name="Bd">
<ContentPresenter />
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="#FF204080"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The trigger is based on Model IsSelected property.
The Grid is named Bd in order to change its background in the trigger.
Link to a full working demo : http://1drv.ms/1iyvPgt
Note
I trie to answer closely to the question .
But in my humble opinion, I would not use a ListView May be you have good reasons I gnore.
I 'd prefer to use an ItemsControl that is a listbox that doesn't allow to make a selection.
I'd put Toggle button, with custom Template to put a blue background if needed.
It would remove all the trigger stuff
, ...
Best coding

check checkbox when item selected in listview UWP

I am writing a UWP app with a listview. The listviewitems contain a textblock and a checkbox. When the listviewitem is selected, I would like the checkbox to check/uncheck. I would also like to remove the "selected" animation, where the listviewitem turns blue when it is selected.
I have found different solutions, but they all seem to rely on the use of Triggers, which Visual Studio tells me is not available in UWP.
How can I solve this, without triggers in UWP?
My listview:
<ListView Name="ListViewItems" Grid.Row="2">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Margin" Value="0,0,0,1"></Setter>
<Setter Property="Background" Value="White"></Setter>
<Setter Property="Padding" Value="5"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Padding="5,0">
<CheckBox HorizontalAlignment="Right" VerticalAlignment="Center" Name="CheckBoxItem"></CheckBox>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Name="TextblockItem" Text="{Binding}"></TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When the listviewitem is selected, I would like the checkbox to check/uncheck.
You can directly binding the IsChecked property to the CheckBox to the ListViewItem IsSelected property:
<CheckBox
HorizontalAlignment="Right"
Margin="10"
VerticalAlignment="Center"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}}"
Name="CheckBoxItem">
</CheckBox>
Whenever the IsSelected Property of ListViewItem change, the CheckBox will be checked/unchecked.
I would also like to remove the "selected" animation, where the
listviewitem turns blue when it is selected.
The code below can help you achieve this, BUT, it overrides the Template of the Item, which means that you have to write your own template.
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If your ItemSource of the ListView is bound to a collection on a ViewModel, I would suggest you add a bool to the item class of the collection.
So for example if you are showing Persons, add a bool IsSelected field to the Person class.
You can then use this field to bind it to the IsChecked prop of the checkbox in your view, only thing needed to do is be sure to set the Mode=TwoWay
Last thing left to do is toggle this IsSelected field, you do this by binding the SelectedItem of the ListView to a Person property on your ViewModel and each time this is being called ( setter of the property ), toggle the given IsSelected field.
To override the change in colour when an item is selected of the ListView, you need to get a copy of the ListView template and delete the colour setting in the selected state

Datagrid and Listview with same itemssource

I have a WPF application with a DataGrid and ListView that share the same ObservableCollection ItemsSource. When the DataGrid's CanUserAddRows property is True it causes the ListView to display the extra item that the DataGrid uses to add new rows.
How can I get the extra row from the DataGrid to not show in the ListView?
I tried using a trigger on the ListView's DataTemplate and checking if the items Id was empty or 0
`<ListView.ItemTemplate>
<DataTemplate>
<Label Margin="-2,0,0,0" Name="CategoryLabel" >
<TextBlock TextWrapping="Wrap" Text="{Binding categoryName}" Height="46"></TextBlock>
</Label>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding categoryId}" Value="0" > <!-- also tried Value="" -->
<Setter TargetName="CategoryLabel" Property="Visibility" Value="Hidden" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>`
I just posted an answer to a problem of changing the template using a data template selector
Change View with its ViewModel based on a ViewModel Property
Possibly just because I have recently looked at this but I wonder if it might be possible to use the same technique here.
Have one template for where the category has a value,then another blank template for values without a category. The important part is you do the test in code rather than XAML so easier to inspect.
You can solve your problem without any modification of your ViewModel or code behind. You can do well without explicitly defining CollectionView's of any kind. Just add to your view's XAML one more (or only) DataTrigger that triggers on the NewItemPlaceholder item of the default view of ListView ItemsSource's collection. Have this trigger to set the UIElement.Visibility attached property to "Hidden". Place it within ItemContainerStyle style triggers. Like this:
<ListView
ItemsSource="{Binding ...}"
>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
...
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding}"
Value="{x:Static CollectionView.NewItemPlaceholder}">
<Setter Property="UIElement.Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="..." Value="{Binding ...}" />
...
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label Margin="..." Name="...">
<TextBlock TextWrapping="Wrap"
Text="{Binding ...}" />
</Label>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>

Using BasedOn and Trigger to manipulate nested property in parent Style

We have a file of standard styles. One style, SectionGroup, we use on all our GroupBox elements. It has a custom template which uses a Border to put an underline below the header, among other things.
On one page, we have a checkbox next to a GroupBox header; when the user unchecks the checkbox, the contents of the GroupBox hide (visibility collapsed) and the header remains. Unfortunately the underline under the header then looks ugly; we also want to hide this.
I've given it my best attempt, so the parent SectionGroup style now looks like this:
<Style x:Key="SectionGroup" TargetType="GroupBox">
<Style.Resources>
<Thickness x:Key="HeaderBorderThickness">0,0,0,1</Thickness>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupBox">
<Grid Margin="0">
...
<Border Grid.Row="0" BorderThickness="{DynamicResource HeaderBorderThickness}" >
<TextBlock Text="{Binding Header, RelativeSource={RelativeSource AncestorType=GroupBox}}"/>
</Border>
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
By defining the HeaderBorderThickness resource and using it as a DynamicResource, I can override it in my page (as explained in this answer):
<GroupBox>
<GroupBox.Style>
<Style TargetType="GroupBox" BasedOn="{StaticResource SectionGroup}">
<Style.Resources>
<Thickness x:Key="HeaderBorderThickness">0,0,0,0</Thickness>
</Style.Resources>
<!-- TODO triggers here.. -->
</Style>
</GroupBox.Style>
<GroupBox.Header>Section One</GroupBox.Header>
...contents...
</GroupBox>
So indeed, by redefining a Thickness of the same key, the DynamicResource works as expected and there is no underline on the header.
Now I need to toggle it based on a trigger/binding. I'm pretty new to this, but elsewhere in this page I have figured out to do stuff like this:
<Grid Visibility="{Binding Path=FooBoolean, Converter={StaticResource BooleanToVisibility}}">
I think there's a little more magic involved in our viewmodel class (followed the example of existing bindings & properties), but it works.
Now the question is -- how do I bind the boolean value in FooBoolean, to the HeaderBorderThickness resource value? Or what other means can I use to accomplish my goal?
It seems to me that you could do this in a much more WPF way with a DataTrigger, perhaps something like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Header, RelativeSource={RelativeSource AncestorType=
GroupBox}}"/>
<Rectangle Grid.Row="1" Height="1" Margin="0,2,0,0" Fill="Black">
<Rectangle.Style>
<Style>
<Setter Property="Rectangle.Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{IsChecked, ElementName=YourCheckBox}"
Value="False">
<Setter Property="Rectangle.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</Grid>
This method enables you to set the Width, Height, Padding and whatever other properties of the line. If you can't access the CheckBox directly from the Style, then you could try adding a bool property to bind to both the Checkbox.IsChecked property and the DataTrigger.Binding property. Or just manage the Rectangle.Visibility in your own method.

Categories