I am trying to implement a CollectionView as in the picture below. As you can see, if an element is selected, it has a corresponding check mark in the upper right corner
First I tried to find the AbstractLayout category ID in the SelectionChanged event, and already inside it I was looking for an element named = "showIfSelected", but when searching inside collectionView I always got null. I have read several articles about why this is the case, but I have not found a solution to the problem. Maybe someone can tell me how to achieve the result I need in the end?
.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:Paraglider.MobileApp.ViewModels"
xmlns:models="clr-namespace:Paraglider.MobileApp.Models"
x:DataType="viewmodels:CatalogPageViewModel"
x:Class="Paraglider.MobileApp.Pages.CatalogPage">
<Grid BackgroundColor="#FFFEF6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10*" />
</Grid.RowDefinitions>
...
<ScrollView Grid.Row="1" Grid.ColumnSpan="2" Margin="20, 0" VerticalScrollBarVisibility="Never">
<CollectionView
x:Name="collectionView"
Grid.Row="2" Grid.ColumnSpan="2"
ItemsSource="{Binding Categories}"
SelectionMode="Multiple"
SelectionChanged="CollectionView_SelectionChanged">
<CollectionView.Header>...</CollectionView.Header>
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="{OnIdiom Default=2}" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Category">
<AbsoluteLayout x:Name="{Binding Id}">
<Image
Aspect="AspectFit"
WidthRequest="165"
Source="pzv.svg"
Margin="0, 10" />
<Image
x:Name="showIfSelected"
Aspect="AspectFit"
AbsoluteLayout.LayoutBounds="130, 0, autoSize, autoSize"
Source="selected_category.svg"
IsVisible="True" >
<Image.Shadow>
<Shadow Brush="Black" Offset="0, 10" Opacity="0.1" Radius="15" />
</Image.Shadow>
</Image>
</AbsoluteLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ScrollView>
</Grid>
</ContentPage>
.xaml.cs:
public partial class CatalogPage : ContentPage
{
public CatalogPage(CatalogPageViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var prev = e.PreviousSelection.Select(x => (Category)x).ToList();
var current = e.CurrentSelection.Select(x => (Category)x).ToList();
var unselected = prev.ExceptBy(current.Select(x => x.Id), x => x.Id).ToList();
foreach (var item in unselected)
{
var layout = this.FindByName<AbsoluteLayout>($"{item.Id}");
var image = layout.FindByName<Image>("showIfSelected");
image.IsVisible = false;
}
var selected = current.ExceptBy(prev.Select(x => x.Id), x => x.Id).ToList();
foreach (var item in selected)
{
var layout = this.FindByName<AbsoluteLayout>($"{item.Id}");
var image = layout.FindByName<Image>("showIfSelected");
image.IsVisible = true;
}
}
}
Combine styling:
https://learn.microsoft.com/en-us/dotnet/maui/user-interface/styles/xaml?view=net-maui-7.0
And visual states:
https://learn.microsoft.com/en-us/dotnet/maui/user-interface/visual-states?view=net-maui-7.0
You can use the VisualStates, to determine what style to be used.
CollectionView has a VisualState Selected.
You can name your visual element, lets say that picture, and use styling to change properties of exactly that visual element.
No need of this wall of text, everything can be done in the XAML, with zero code behind it.
Edit: Other users suggested, you may need example how to do it.
Here is a working code, from one of my test projects (on MAUI NET 7.0)
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="Stroke".../>
<Setter Property="BackgroundColor" ..."/>
<Setter TargetName="line1"
Property="Label.TextColor"
Value="Black" />...
The important part you can notice, is that I have a Label VisualElement, that has the Name "line1", and I access the property TextColor, and apply the value Black, when the item gets selected.
This throws terrible runtime exceptions, if you apply this style, and fail to provide child VisualElement of type Label, and the specified Name.
On the plus side, the alternative to this is more time consuming (in magnitudes) and also prone to errors.
I hope this helps.
As suggested by Jason, you can directly bind IsVisible to a property of your Model like below:
XAML:
<Image
Aspect="AspectFit"
Source="selected_category.png"
IsVisible="{Binding IsSelected, Mode=TwoWay}" >
</Image>
Model:
public partial class Category : ObservableObject
{
[ObservableProperty]
private bool isSelected;
}
Related
I want to change the background color of the Frame. I tried many ways like setting the Focused event, Triggers and Binding of background color property nothing works for me. This is my xaml code.
<StackLayout Padding="10">
<CollectionView x:Name="list" ItemsSource="{Binding samplelist}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="2" HorizontalItemSpacing="10" VerticalItemSpacing="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Green" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Frame CornerRadius="10" HasShadow="False" BackgroundColor="{Binding BackgroundTest,Mode=TwoWay}" HeightRequest="75" Margin="5,0,0,0" >
<StackLayout Orientation="Vertical">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference test}, Path=BindingContext.textselected}"
CommandParameter="{Binding .}"/>
</StackLayout.GestureRecognizers>
This my code in view model
private string _backgroundTest;
public string BackgroundTest
{
get { return _backgroundTest; }
// set => SetProperty(ref _backgroundTest, value);
set
{
if (value == _backgroundTest)
return;
_backgroundTest = value;
OnPropertyChanged(nameof(BackgroundTest));
}
}
private async void clicked(Test test)
{
BackgroundTest = "#F95F62";
}
I referred the following links
How to change color of Frame control when clicked in Xamarin.Forms with MVVM
Xamarin how to change background color on button click MVVM
But nothing worked.I have no clue how to fix this. Any suggestions?
BackgroundColor expects a Color, but you are returning a string. Try using a Converter, or just use a Color property instead of a string
Option A: Use a Value converter to convert a string to Color (check the documentation)
Create the converter class
Add it to your App.Xaml
Modify your Frame binding Xaml
<Frame CornerRadius="10" HasShadow="False" BackgroundColor="{Binding BackgroundTest,Mode=TwoWay, Converter={StaticResource colorConverter}}" HeightRequest="75" Margin="5,0,0,0" >
Option B: Modify your binding to be a Color type
private Color _backgroundTest;
public Color BackgroundTest
{
get { return _backgroundTest; }
set
{
if (value == _backgroundTest)
return;
_backgroundTest = value;
OnPropertyChanged(nameof(BackgroundTest));
}
}
private async void clicked(Test test)
{
BackgroundTest = ColorConverters.FromHex("#F95F62");
}
For both cases:
In your code the BackgroundColor of the Frame is inside a CollectionView, so the binding will be looking for the property inside the Item. It should look it inside the ViewModel
BackgroundColor="{Binding Source={RelativeSource AncestorType={x:Type local:ItemsViewModel}},Path=BackgroundTest}"
I have this ListView with 2 buttons, on each List/Item (Plus and Minus Buttons) that appears only on selected item.
In addition to those buttons, the selected item will also change the color to LightGray and the previews item will be Transparent (which is working great).
But the problem I have is when I select an Item and then click on the Plus / Minus Button, it’s clearing the selected item color.
So only the buttons will stay on, but the BackgroundColor is gone.
I’m trying to keep the LightGray color all the time until I select a different item.
Since I’m new to this It will be great if you can help me with some example (If possible please).
If need more Info, please let me know.
Thank you so much.
ItemsPage.xaml
<ListView ItemsSource="{Binding Items, Mode=TwoWay}" x:Name="lstView" SelectedItem="{Binding SelectedItem}" HasUnevenRows="True" RowHeight="50">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="0,0,8,0" Margin="4,0,4,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="7*"/>
</Grid.ColumnDefinitions>
<Grid.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="TapGestureRecognizer_Edit1" CommandParameter="{Binding ItemId}"/>
</Grid.GestureRecognizers>
<Label Text="{Binding ItemName}" Grid.Column="1" FontSize="Medium"></Label>
<Image Source="DecreaseIcon1.png" Grid.Column="2" Margin="4" IsVisible="{Binding IsVisible}">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="DecrementQuantity" CommandParameter="{Binding ItemId}"/>
</Image.GestureRecognizers>
</Image>
<Entry Text="{Binding ItemQuantity}" Grid.Column="3" VerticalTextAlignment="Center"/>
<Image Source="IncreaseIcon1.png" Grid.Column="4" Margin="4" IsVisible="{Binding IsVisible}">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="IncrementQuantity" CommandParameter="{Binding ItemId}"/>
</Image.GestureRecognizers>
</Image>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ItemsPage.cs
private void IncrementQuantity(object sender, EventArgs e)
{
TappedEventArgs tappedEventArgs = (TappedEventArgs)e;
Item item = ((ItemViewModel)BindingContext).Items.Where(x => x.ItemId == (Guid)tappedEventArgs.Parameter).FirstOrDefault();
var oldQuantity = item.ItemQuantity++;
}
private void DecrementQuantity(object sender, EventArgs e)
{
TappedEventArgs tappedEventArgs = (TappedEventArgs)e;
Item item = ((ItemViewModel)BindingContext).Items.Where(x => x.ItemId == (Guid)tappedEventArgs.Parameter).FirstOrDefault();
var oldQuantity = item.ItemQuantity--;
}
Grid lastGrid;
private void TapGestureRecognizer_Edit1(object sender, EventArgs e)
{
TappedEventArgs tappedEventArgs = (TappedEventArgs)e;
Item item = ((ItemViewModel)BindingContext).Items.Where(x => x.ItemId == (Guid)tappedEventArgs.Parameter).FirstOrDefault();
_item = item;
var grid = sender as Grid;
lstView.SelectedItem = grid.BindingContext;
if (lastGrid != null)
{
lastGrid.BackgroundColor = Color.Transparent;
}
var viewCell = (Grid)sender;
if (viewCell.BackgroundColor != null)
{
viewCell.BackgroundColor = Color.LightGray;
lastGrid = viewCell;
}
}
According to your description, I suggest you can use CollectionView to replace ListView. Then using Xamarin.Forms Visual State Manager to highlight selecteditem backgroundcolor.
This is VisualStateManager in ContentPage.Resource.
<ContentPage.Resources>
<Style x:Key="stacklayoutStyle" TargetType="StackLayout">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="LightGray" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ContentPage.Resources>
Using this style in CollectionView.The selected state will stay on when you click Button.
<CollectionView ItemsSource="{Binding items}" SelectionMode="Single">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Style="{StaticResource stacklayoutStyle}">
<Grid Margin="4,0,4,0" Padding="0,0,8,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Grid.Column="1"
FontSize="Medium"
Text="{Binding ItemName}" />
<Button Grid.Column="2" Text="decrement" />
<Entry
Grid.Column="3"
Text="{Binding ItemQuantity}"
VerticalTextAlignment="Center" />
<Button Grid.Column="4" Text="Increment" />
</Grid>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Well, the listview lost it's focus when you tap on any other view like a button or something else. And when the listview is not focused its background color will be lost.
However, trying to set the background color of the listView directly may give you what you want. Note: I haven't tried this code on my PC to see if it solves your problem but give it a try and let me know your result.
if (viewCell.BackgroundColor != null)
{
lstView.BackgroundColor = Color.LightGray;
// Other lines of code
}
I have a GridView as my zoomed out view in a SemanticZoom control. This GridView uses a custom DataTemplateSelector as the ItemTemplateSelector. The DataTemplateSelector returns a DataTemplate with different Foreground color, depending upon whether or not the Group has any items in it.
However, even though the DataTemplateSelector seems to work and returns the correct template, only one template is ever used by the GridView and the text is all the same color.
Here is the XAML of the GroupedListView:
<UserControl
x:Class="GroupList.GroupList.GroupedListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GroupList.GroupList"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="using:GroupList.Model"
xmlns:wuxdata="using:Windows.UI.Xaml.Data"
mc:Ignorable="d">
<UserControl.Resources>
<!-- Use a collection view source for content that presents itself as a list of items that can be grouped or sorted. Otherwise, you can use x:Bind
directly on the ListView's item source to for further optimization. Please see the AppUIBasics sample for an example of how to do this. -->
<CollectionViewSource x:Name="ContactsCVS" IsSourceGrouped="True" />
<Style TargetType="TextBlock" x:Key="TileHeaderTextStyle" BasedOn="{StaticResource ProximaNovaSemiBold}">
<Setter Property="FontSize" Value="54" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontWeight" Value="ExtraBold"/>
<Setter Property="FontStretch" Value="Expanded" />
</Style>
<Style TargetType="TextBlock" x:Key="TileHeaderTextStyleGray" BasedOn="{StaticResource TileHeaderTextStyle}">
<Setter Property="Foreground" Value="Khaki" />
</Style>
<!-- When using x:Bind, you need to set x:DataType -->
<DataTemplate x:Name="ContactListViewTemplate" x:DataType="data:Contact">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse x:Name="Ellipse"
Grid.RowSpan="2"
Width ="32"
Height="32"
Margin="6"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Fill="LightGray"/>
<TextBlock Grid.Column="1"
Text="{x:Bind Name}"
x:Phase="1"
Style="{ThemeResource BaseTextBlockStyle}"
Margin="12,6,0,0"/>
<TextBlock Grid.Column="1"
Grid.Row="1"
Text="{x:Bind Position}"
x:Phase="2"
Style="{ThemeResource BodyTextBlockStyle}"
Margin="12,0,0,6"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="GrayZoomedOutTemplate" x:DataType="wuxdata:ICollectionViewGroup">
<TextBlock Text="{x:Bind Group.(data:GroupInfoList.Key)}" Margin="0,0,0,5" Style="{StaticResource TileHeaderTextStyleGray}" />
</DataTemplate>
<DataTemplate x:Key="ZoomedOutTemplate" x:DataType="wuxdata:ICollectionViewGroup">
<TextBlock Text="{x:Bind Group.(data:GroupInfoList.Key)}" Margin="0,0,0,5" Style="{StaticResource TileHeaderTextStyle}" />
</DataTemplate>
<local:GroupEmptyOrFullSelector x:Key="GroupEmptyOrFullSelector" Empty="{StaticResource GrayZoomedOutTemplate}" Full="{StaticResource ZoomedOutTemplate}" />
</UserControl.Resources>
<!--#region Navigation Panel -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical">
<TextBlock Margin="15,0,0,0" Text="Paula's SemanticZoom Sandbox" Grid.Row="0"
VerticalAlignment="Center"
Style="{ThemeResource TitleTextBlockStyle}" />
<Button x:Name="ZoomInOutBtn" Content="ABC↕" Click="ZoomInOutBtn_Click" Width="60" HorizontalAlignment="Center" BorderThickness="0" />
</StackPanel>
<!--#endregion-->
<SemanticZoom x:Name="ZoomControl" Grid.Row="1">
<SemanticZoom.ZoomedInView>
<GridView ItemsSource="{x:Bind ContactsCVS.View}"
ItemTemplate="{StaticResource ContactListViewTemplate}"
SelectionMode="Single"
ShowsScrollingPlaceholders="True">
<GridView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="data:GroupInfoList">
<TextBlock Text="{x:Bind Key}"
Style="{ThemeResource TitleTextBlockStyle}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<GridView ItemTemplateSelector="{StaticResource GroupEmptyOrFullSelector}" ScrollViewer.VerticalScrollBarVisibility="Disabled" Margin="0, 200" Width="475" ItemsSource="{x:Bind ContactsCVS.View.CollectionGroups}" SelectionMode="None" >
</GridView>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
</Grid>
</UserControl>
And here is the DataTemplateSelector:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.Foundation.Collections;
using GroupList.Model;
namespace GroupList.GroupList
{
/// <summary>
/// This determines whether or not the Group passed during binding is empty or not and allows selection
/// of the proper DataTemplate based on this value.
/// </summary>
class GroupEmptyOrFullSelector : DataTemplateSelector
{
private DataTemplate _full;
private DataTemplate _empty;
public DataTemplate Full
{
set { _full = value; }
get { return _full; }
}
public DataTemplate Empty
{
set { _empty = value; }
get { return _empty; }
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var itemType = item.GetType();
var isGroup = itemType.Name == "GroupInfoList";
bool isEmpty = false;
GroupInfoList groupItem;
if (isGroup)
{
groupItem = item as GroupInfoList;
isEmpty = groupItem.Count == 0;
}
// Disable empty items
var selectorItem = container as SelectorItem;
if (selectorItem != null)
{
selectorItem.IsEnabled = !isEmpty;
}
if (isEmpty)
{
return Empty;
}
else
{
return Full;
}
}
}
}
So, here was the problem:
In the DataTemplateSelector listing, even if a passed "object item" was not a group, it fell through to the template selection logic. Sometimes, the "object" passed to your function is not a Group object, but rather a generic DependencyObject. In such a case, your template selection logic needs to return a default template, returning one of the specific templates only if the "object item" is a Group object. Thus, the new function looks like this:
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var itemType = item.GetType();
var isGroup = itemType.Name == "GroupInfoList";
bool isEmpty = false;
GroupInfoList groupItem;
if (isGroup)
{
groupItem = item as GroupInfoList;
isEmpty = groupItem.Count == 0;
// Disable empty items
var selectorItem = container as SelectorItem;
if (selectorItem != null)
{
selectorItem.IsEnabled = !isEmpty;
}
if (isEmpty)
{
return Empty;
}
else
{
return Full;
}
}
return Full;
}
Hi fellow programmers,
I'm working on a WPF software that uses a Canvas to display and move graphic objects.
When the user clic on an object, I need to display a panel with the selected object's properties.
These properties are different for each object, one can have a displayed text, another a background color or a scale value.
What is the best way to program this ?
I have 9 objects type, I'm searching for something more elegant than creating my controls in panels and switch betwenn then for every graphic object type.
Thank you for your help.
Edit - to show design code :
The dock panel for generated Wpf controls to display properties.
<DockPanel x:Name="pnlProperties" Width="200" Grid.Column="2" Background="red">
<Grid x:Name="GridProperties" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Margin="0,2,0,25" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="95"/>
<RowDefinition Height="95"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ***** Label ***** -->
<Label x:Name="lblLabel1" Content="test Prop" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="0" FontSize="16"/>
<Label x:Name="lblLabel2" Content=" Prop 2" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="1" Grid.Column="0" FontSize="16"/>
<Label x:Name="lblLabel3" Content=" Prop 3" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="2" Grid.Column="0" FontSize="16"/>
<Label x:Name="lblLabel4" Content=" Prop 4" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="3" Grid.Column="0" FontSize="16"/>
</Grid>
</Grid>
</DockPanel>
The Canvas that displays the MovableObject (userControl) of each graphic objects :
<UserControl x:Class="DashEditor.Views.ScreenView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DashEditor.Views">
<Canvas x:Name="ObjectsCanvas" HorizontalAlignment="Center" VerticalAlignment="Center" Width="800" Height="480" AllowDrop="True" Background="Black" PreviewMouseLeftButtonDown="ObjectsCanvas_PreviewMouseLeftButtonDown" >
<Image x:Name="imgFond" Stretch="Fill" Source="/DashEditor;component/assets/FondXAP.png" Width="800" Height="480"/>
</Canvas>
One of the graphic object class :
[StructLayout(LayoutKind.Sequential)]
[XmlRoot("XapLabel")]
public class XapLabel : IXapGraphicObject
{
private int _id;
public int Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
[XmlIgnore]
private MovableObject _Control;
[XmlIgnore]
public MovableObject Control
{
get
{
return _Control;
}
set
{
_Control = value;
}
}
private Point _pos;
public Point Pos
{
get
{
return _pos;
}
set
{
_pos = value;
}
}
public IXapGraphicObject getXapParent(MovableObject Control)
{
return this;
}
public ObjectType Type
{
get
{
return ObjectType.Label;
}
}
public XapLabel()
{
}
public void ConnectToMoveEvent()
{
_Control.OnObjectTranslating += _Control_OnObjectTranslating;
}
private void _Control_OnObjectTranslating(Vector displacement)
{
Pos = Pos + displacement;
}
}
I've done something similar to this, if you are familiar with MVVM:-
For the canvas, I used an ItemsControl bound to an ObservableCollection of your "graphic objects", to which you'll be adding objects that you want to appear on the canvas. You'll also need to change the ItemsControl's panel template to a Canvas:-
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="800" Height="480" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Your "graphic object" classes need to expose double properties (say "X" and "Y"), to control the object's position on the canvas.
Next, create a XAML DataTemplate for each of these classes, to define their visual appearance. The data template should include the following bindings:
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
For the property grid, rather than write your own, look at the free Xceed Toolkit community edition (here), which has a very good PropertyGrid control. You bind its SelectedObject property to the selected object, but read the documentation - there are plenty of decent features.
(If you are using MVVM then remember to change your classes to implement INotifyPropertyChanged, and raise the PropertyChanged event in the setters).
For the drag and drop functionality, you should just be able to set the selected object's X and Y values within the mouse move event.
Not a full solution I know, but will hopefully point you in the right direction.
I have the following code:
<Window.Resources>
<DataTemplate x:Key="SectionTemplate" >
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</Window.Resources>
<Grid >
<Border>
<HeaderedItemsControl Header="Top1"
ItemsSource="{Binding Path=List1}"
ItemTemplate="{StaticResource SectionTemplate}"/>
</Border>
</Grid>
public class MainWindow
{
public List<Item> List1
{
get { return list1; }
set { list1 = value; }
}
public MainWindow()
{
list1.Add(new Item { Name = "abc" });
list1.Add(new Item { Name = "xxx" });
this.DataContext = this;
InitializeComponent();
}
}
public class Item
{
public string Name { get; set; }
}
For some reason the Items are rendered, but without the header.
As the documentation points out:
A HeaderedItemsControl has a limited default style. To create a HeaderedItemsControl with a custom appearance, create a new ControlTemplate.
So when you create that template make sure to include some ContentPresenter which is bound to the Header (e.g. using ContentSource)
e.g.
<HeaderedItemsControl.Template>
<ControlTemplate TargetType="{x:Type HeaderedItemsControl}">
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ContentPresenter ContentSource="Header" />
<Separator Grid.Row="1" />
<ItemsPresenter Grid.Row="2" />
</Grid>
</Border>
</ControlTemplate>
</HeaderedItemsControl.Template>
(All the default bindings (Margin, Background, etc.) are omitted.)
You can make a DataTemplate for the header, just as you did for the items (which is surely less intrusive than redoing the entire control template).
e.g.
<Window.Resources>
<DataTemplate x:Key="HeaderTemplate">
</DataTemplate>
</Window.Resources>
<HeaderedItemsControl Header="Top1"
HeaderTemplate="{StaticResource HeaderTemplate}"
ItemsSource="{Binding Path=List1}"
ItemTemplate="{StaticResource SectionTemplate}"
/>
Instead of using e.g. "Top1" as here, you can bind to an object and then use binding relative to that object in the DataTemplate.
There is one gotcha with this approach, which is that the necessary approach to getting styles pulled in for non-control elements (notably TextBlock) is a little convoluted; also see WPF Some styles not applied on DataTemplate controls. (You might also run into this with the ControlTemplate approach.)