How can I bind a context menu on a LineSymbol in ESRI? - wpf

I have created a ControlTemplate for a LineSymbol:
<esri:SimpleLineSymbol
x:Key="PolylineSymbol"
Width="3"
>
<esri:SimpleLineSymbol.ControlTemplate>
<ControlTemplate>
<Grid
>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
...
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionStates">
...
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path
x:Name="Element" Fill="{x:Null}"
Stroke="Navy" StrokeThickness="3"
StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path.ContextMenu>
<ContextMenu
x:Name="popUpMenu"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type esri:Graphic}}}">
<MenuItem
x:Name="miSelect"
Header="Select"
IsCheckable="True"
IsChecked="{Binding Selected, FallbackValue=False}"
/>
...
</ContextMenu>
</Path.ContextMenu>
</Path>
</Grid>
</ControlTemplate>
</esri:SimpleLineSymbol.ControlTemplate>
</esri:SimpleLineSymbol>
Everything works well, except the binding in IsChecked on the "Select" menu item: Since neither Symbol, nor Graphic inherits from FrameworkElement no binding expression will take me from here to the Contained Graphic of this SimpleLineSymbol.
I have also tried a Click event (which gives a sender, a command (which supports a parameter) or a MouseRightButtonDown event (on the graphic,) - no method takes me from the right-clicked point on the path of the Symbol to the containing Graphic...
The DataContext of the Menu looks OK in the designer of VS2012, but at run time it does not works, since the Menu is inside a path defined in the ControlTemplate of a Symbol which is not a FrameworkElement!
I have added a name for the ContextMenu, but I am not able to retrieve it from the ViewModel (where I create the graphic and the symbol; if I were able to do that, I would be able to add the desired datacontext in code:
var graphic = new Graphic { Symbol = Resources["PolylineSymbol"] as SimpleLineSymbol;
var menu = graphic.Symbol.ControlTemplate.FindName("popUpMenu", graphic.Symbol); // ???
menu.DataContext = graphic;
)
Any Ideas, please?

If I understand your problem correctly, it seems as though you have a common problem in WPF. The solution is to utilise a Tag property of your Path to 'pass' the DataContext through to the ContextMenu using the ContextMenu.PlacementTarget property. This Gets or sets the UIElement relative to which the ContextMenu is positioned when it opens. Try this:
<Path x:Name="Element" Fill="{x:Null}" Stroke="Navy" StrokeThickness="3"
StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round"
Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type
esri:Graphic}}}"><!--Use Binding.Path that you need for data here-->
<Path.ContextMenu>
<ContextMenu x:Name="popUpMenu" DataContext="{Binding PlacementTarget.Tag,
RelativeSource={RelativeSource Self}}"><!--Use PlacementTarget.Tag-->
<MenuItem x:Name="miSelect" Header="Select" IsCheckable="True"
IsChecked="{Binding Selected, FallbackValue=False}" />
...
</ContextMenu>
</Path.ContextMenu>
</Path>

The solution was to implement the Opened even of the Context menu. In the code behind I assign the instance of the view model on its DataContext.
<Path.ContextMenu>
<ContextMenu
Opened="PopUpMenu_OnOpened"
>
code behind:
private void PopUpMenu_OnOpened(object sender, RoutedEventArgs e)
{
var menu = sender as ContextMenu;
if (menu != null)
{
menu.DataContext = ViewModel;
}
}
Another challenge was to get the clicked graphic and the clicked point.
The solution was to create properties in the View model and assign both on LeftMouseDown and RightMouseDown.
private Graphic GetPolylineGraphic(ESRI.ArcGIS.Client.Geometry.Geometry geometry = null)
{
var drawLayer = Model.GetDrawLayer(MyMap, "Polyline");
var graphic = new Graphic
{
// clone the resourced PolylineSymbol (from Model)
Symbol = new SimpleLineSymbol
{
Color = PolylineSymbol.Color,
Width = PolylineSymbol.Width,
ControlTemplate = PolylineSymbol.ControlTemplate
}
};
if (geometry != null) graphic.Geometry = geometry;
graphic.MouseLeftButtonDown += GraphicOnMouseLeftButtonDown;
graphic.MouseRightButtonDown += GraphicOnMouseRightButtonDown;
drawLayer.Graphics.Add(graphic);
return graphic;
}
private Graphic m_clickedGraphic;
public Graphic ClickedGraphic
{
get { return m_clickedGraphic; }
set
{
if (!Equals(m_clickedGraphic, value))
{
m_clickedGraphic = value;
OnPropertyChanged(value);
}
}
}
private MapPoint m_clickedPoint;
public MapPoint ClickedPoint
{
get { return m_clickedPoint; }
set
{
if (m_clickedPoint != value)
{
m_clickedPoint = value;
OnPropertyChanged(value);
}
}
}
private void GraphicOnMouseRightButtonDown(object sender, MouseButtonEventArgs args)
{
//// This does not work because GraphicElement is internal!!!
//var s = args.Source;
//ClickedGraphic = ((GraphicElement)(e.Source)).Graphic;
//ClickedPoint = ((GraphicElement)(e.Source)).Origin;
ClickedGraphic = sender as Graphic;
ClickedPoint = MyMap.ScreenToMap(args.GetPosition(MyMap));
//// not here - else context menu won't pop!
//args.Handled = true;
}
private void GraphicOnMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
{
var g = sender as Graphic;
if (g != null)
{
ClickedGraphic = g;
ClickedPoint = MyMap.ScreenToMap(args.GetPosition(MyMap));
// select/unselect the graphic on left click
if (g.Selected) g.UnSelect();
else g.Select();
args.Handled = true;
}
}
To make everything work, I had to clone the symbol.

Related

Custom ListBox selection in WPF

I have a Custom ListBox with multiple columns per one Item
<ListBox Name="UserListBox" Loaded="GetUsers_OnLoad" SelectionChanged="UserSelected">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel Name="UserDockPanel" Margin="4">
<TextBlock Name="UsernameTextBlock" Text="{Binding Path=Username}"/>
<CheckBox Name="OneCheckBox" IsHitTestVisible="False" IsChecked="{Binding One}" />
<CheckBox Name="TwoCheckBox" IsHitTestVisible="False" IsChecked="{Binding Two}" />
<CheckBox Name="ThreeCheckBox" IsHitTestVisible="False" IsChecked="{Binding Three}" />
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
What I am trying to do is when the user selects an item that I can parse the individual values for that item (UsernameTextBlock, OneCheckbox, TwoCheckBox, ThreeCheckBox).
I have tried selected which throws an error and selection changed seems to work but I do not know how to retrieve the individual values for the item selected.
Any insight would be appreciated.
UPDATE:
Here is the code behind
private void UserSelected(object sender, RoutedEventArgs e)
{
var userListBox = FindName("UserListBox") as ListBox;
var selectedItem = userListBox.SelectedItem as ListBoxItem;
MessageBox.Show(selectedItem.Username);
}
I am currently just showing a message popup to show what I am accessing
UPDATE 2:
private void GetUsers_OnLoad(object sender, RoutedEventArgs e)
{
_outreachAuths = _outreachTableAdapter.GetOutreachAuths();
var users = new List<UserItem>();
foreach (DataRow row in _outreachAuths.Rows)
{
users.Add(new UserItem() { Username = row.ItemArray[0].ToString(), One = false, Two = true, Three = ((row.ItemArray[2].ToString() == "1"))});
}
var userList = sender as ListBox;
if (userList != null) userList.ItemsSource = users;
}
In your UserSelected handler you're casting the selected item to type ListBoxItem:
var selectedItem = userListBox.SelectedItem as ListBoxItem;
In order to access the properties you're looking for you'll need to cast it to its original type which is, I believe, UserItem.
var selectedItem = userListBox.SelectedItem as UserItem;
Bind the listbox's SelectedItem property to a property in your view model. You will then have access to the item when it's value changes in the VM.
<ListBox Name="UserListBox" Loaded="GetUsers_OnLoad" SelectionChanged="UserSelected" SelectedItem={Binding Path=PropertyOnViewModel}>

drawing new lines in an already populated canvas

So I got on my grid a canvas which as its background it get a drawing brush from resource dictionary.That not a problem it works perfect …
But now I need to draw an open ended polygon on it , which gets its coordinates as an array of X and Y. let’s say intX[i] and intY[i] arrays…. So point 1 is intX[0] and intY[0] and so on ….
And after that depending on some calculation I will get some more attributes and I need to add some horizontals and vertical lines ‘no more polygons’ .
After that I need to write the result on it … so on point (x1,y1) I need to WRITE the result
Ps : is this even possible …. Is canvas a good choice ‘ I chose canvas because the coordinates should be absolute and doesn’t change when window or object is resized’ the newly drawn lines should not delete the previous drawn line or the background’
Sorry or my bad English and I hope u can guide me with something to start with….
Not sure I fully understand your need but you could use a custom ItemsControl.
First you need an entity to store all the infos for a single data-point, its position and label:
public class DataPoint : INotifyPropertyChanged
{
private double left;
public double Left
{
get { return left; }
set
{
if (value != left)
{
left = value;
PropertyChanged(this, new PropertyChangedEventArgs("Left"));
}
}
}
private double top;
public double Top
{
get { return top; }
set
{
if (value != top)
{
top = value;
PropertyChanged(this, new PropertyChangedEventArgs("Top"));
}
}
}
private string text;
public string Text
{
get { return text; }
set
{
if (value != text)
{
text = value;
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
Then in your VM or directly in your code-behind you could expose it with an ObservableCollection:
public ObservableCollection<DataPoint> Points { get; set; }
Then on the view side:
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" HorizontalAlignment="Center">
<Button Click="GetData_Click">Get Data</Button>
<Button Click="GetText_Click">Get Text</Button>
</StackPanel>
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.Template>
<ControlTemplate>
<Canvas IsItemsHost="True" Background="AliceBlue" Width="500" Height="500"></Canvas>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:DataPoint}">
<Canvas>
<Ellipse Fill="Red" Width="6" Height="6" Canvas.Left="-3" Canvas.Top="-3"></Ellipse>
<TextBlock Text="{Binding Text}" Canvas.Left="10" Canvas.Top="0"></TextBlock>
</Canvas>
</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>
</DockPanel>
local is the mapping of the DataPoint class namespace.
Here are the two event handlers used for demo purposes:
Random rand = new Random();
private void GetData_Click(object sender, RoutedEventArgs e)
{
const int max = 500;
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
}
private void GetText_Click(object sender, RoutedEventArgs e)
{
foreach (DataPoint p in Points)
{
p.Text = ((char)('a' + rand.Next(26))).ToString();
}
}
Hope this helps.

Get selected TreeViewItem in MVVM

I want make TreeView with editable nodes. I googled this good, as I think, article:
http://www.codeproject.com/Articles/31592/Editable-TextBlock-in-WPF-for-In-place-Editing
But I have a problems. My TreeView formed dinamically, not statically as in the arcticle. Like that
<TreeView Name="_packageTreeView" Margin="5" ItemsSource="{Binding PackageExtendedList}">
<TreeView.InputBindings>
<KeyBinding Key="C" Command="{Binding *TestCommand*}" CommandParameter="{Binding}" />
</TreeView.InputBindings>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding PackageTreeItemChangeCommand}" CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type MasterBuisnessLogic:RootDocPackage}" ItemsSource="{Binding Path=Childs}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition>
</ColumnDefinition>
<ColumnDefinition>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Source="/Resources/DocGroup.png"></Image>
<Etb:EditableTextBlock Margin="5,0,0,0" Grid.Column="1" Text="{Binding Path=Name}"></Etb:EditableTextBlock>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
PackageExtendedList - List of DocPackageExtended.
So, first question - how can I get TreeViewItem instance in TestCommand? Not instance DocPackageExtended class! I want to get instance selected TreeViewItem like in the article.
And second question - After I get instance TreeViewItem, how can I get EditableTextBlock from the TreeView item's DataTemplate.
added answer
I already tried it. Cause in MVVM ViewModel cannot has any link to View object like TreeView, I make handler in code-behind, like that
private void TreeViewItemSelected(object sender, RoutedEventArgs e)
{
// Already have TreeViewItem instance without of ItemContainerGenerator help
var tvi = e.OriginalSource as TreeViewItem;
if (tvi == null)
return;
var etb = VisualTreeLib.VisualTreeLib.GetVisualChild<EditableTextBlock>(tvi);
if (etb == null)
return;
// Do what I want
etb.IsEditable = true;
}
Unfortunately, this has no any affect :(
I also tried that approach, but also failed.
in DocPackageExtended type I define property
public bool IsEditable
{
get { return _isEditable; }
set
{
_isEditable = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsEditable"));
}
}
than change in XAML:
<Etb:EditableTextBlock Margin="5,0,0,0" Grid.Column="1" Text="{Binding Path=Name}" *IsEditable="{Binding Path=IsEditable}"*/>
and in ViewModel
private void TestCommandMethod(object obj)
{
var dpe = obj as DocPackageExtended;
if (dpe == null)
return;
dpe.IsEditable = true;
}
Doesn't work too :(
Any ideas?
This might help you.
private void Button_Click(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItemFound = GetItem(MyTreeview, MyTreeview.SelectedItem);
ContentPresenter header = treeViewItemFound.Template.FindName("PART_Header", treeViewItemFound) as ContentPresenter;
if (header != null)
{
TextBox myTextBox = (TextBox)header.ContentTemplate.FindName("MyTextBox", header);
}
}
public TreeViewItem GetItem(ItemsControl container, object itemToSelect)
{
foreach (object item in container.Items)
{
if (item == itemToSelect)
{
return (TreeViewItem)container.ItemContainerGenerator.ContainerFromItem(item);
}
else
{
ItemsControl itemContainer = (ItemsControl)container.ItemContainerGenerator.ContainerFromItem(item);
if (itemContainer.Items.Count > 0)
{
TreeViewItem treeViewItemFound = GetItem(itemContainer, itemToSelect);
if (treeViewItemFound != null)
return treeViewItemFound;
}
}
}
return null;
}
First question: Since it seems that you can select multiple entries, you need to filter all selected entries in TestCommand's executed method:
IEnumerable<DocPackageExtended> selectedEntries = PackageExtendedList.Where(d => d.IsSelected);
If multiple selection is disabled, you could bind the TreeView's selected item to a property in your VM and access this property in TestCommand's method.
Second question: You get the dataitem's container through var container = YourTreeViewInstance.ItemContainerGenerator.ContainerFromItem(dataInstance);. Now you have to go through this container with the help of the VisualTreeHelper until it finds a control of type EditableTextBlock. But I wouldn't do this in a ViewModel, rather in a helper-class or with the help of attached properties.
EDIT: You're binding the IsEditable property of the instances in the Childs property of your DocPackageExtended class to your EditableTextBox, but in your TestCommandMethod you're manipulating the IsEditableproperty of a DocPackageExtended instance directly. You could do the following:
private void TestCommandMethod(object obj)
{
var dpe = obj as DocPackageExtended;
if (dpe == null)
return;
dpe.IsEditable = true;
foreach (RootDocPackage rdp in dpe.Childs)
{
rdp.IsEditable = true;
}
}

WPF, interesting things when binding with TemplatedParent

Control template:
<ControlTemplate x:Key="BasicShape2">
<StackPanel Name="sp">
<Border Name="bd" CornerRadius="3.5" BorderThickness="1" BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent},Path=DataContext.NodeType, Converter={StaticResource NodeTypeColorConverter}, Mode=OneWay}" Height="32" Padding="1">
<TextBlock Name="tbName" Grid.Column="1" Text="" HorizontalAlignment="Center" VerticalAlignment="Bottom" FontSize="16" />
</Border>
</StackPanel>
</ControlTemplate>
a class which this template will apply to:
public class MyThumbEx : Thumb
{
public static readonly DependencyProperty MemberInfoProperty = DependencyProperty.Register("MemberInfo", typeof(FamilyMemberInfo), typeof(MyThumbEx));
public FamilyMemberInfo MemberInfo
{
get { return (FamilyMemberInfo)GetValue(MemberInfoProperty); }
set { SetValue(MemberInfoProperty, value); }
}
public MyThumbEx(ControlTemplate template, FamilyMemberInfo info, Point position)
{
this.MemberInfo = info;
this.DataContext = this.MemberInfo;
this.Template = template;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.ApplyTextContent();
}
public void ApplyTextContent()
{
TextBlock tbName = this.Template.FindName("tbName", this) as TextBlock;
if (tbName != null)
{
tbName.Text = this.MemberInfo.Name;
}
}
}
initialize and display it on a canvas:
public MainWindow()
{
InitializeComponent();
//
FamilyMemberInfo mi = new FamilyMemberInfo();
mi.Name = "someone";
mi.ID = "id1";
MyThumbEx te = new MyThumbEx(Application.Current.Resources["BasicShape2"] as ControlTemplate, mi, new Point(0, 0));
//
this.cvMain.Children.Add(te);
}
These codes work fine, but be noticed that in the control template, I have to set Path=DataContext.NodeType, not just Path=NodeType. I'm new to WPF, and I found that normally, when I did binding without using this template stuff, I didn't need to specify the predicate 'DataContext', right? Why we need here?
Another thing I found is, I can comment out this.DataContext = this.MemberInfo, and change binding path to Path=MemberInfo.NodeType, the code still works fine. Could anyone explain that for me?
Thanks in advance!
If you dont change the DataContext manuelly, every child automatically has the DataContext of its Parent. So if your Window has f.e. the ViewModel as DataContext all of its Controls have access to the ViewModels Properties through {Binding Path=Property}.
But in case of a ControlTemplate the usual typical flow where DataContext just cascades through from the parent to child doesn’t apply here. So you have to set the DataContext first, either through Property="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.Property}" or DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext}" Property="{Binding Path=Property}".
To your second point: It could be, that the ControlTemplate automatically uses the code-behind of its containing Element as DataContext, so you can use the code-behinds properties without setting the DataContext, but I am not 100% sure about this.

Silverlight - Get the ItemsControl of a DataTemplate

I have a Silverlight application that is using a DataGrid. Inside of that DataGrid I have a DataTemplate that is defined like the following:
<Grid x:Name="myGrid" Tag="{Binding}" Loaded="myGrid_Loaded">
<ItemsControl ItemsSource="{Binding MyItems}" Tag="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal" Width="138">
<TextBlock Text="{Binding Type}" />
<TextBox x:Name="myTextBox" TextChanged="myTextBox_TextChanged" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
When a user enters text into the TextBox, I have an event (myTextBox_TextChanged) that must be fired at this point. When that event gets fired, I would like to get the ItemsControl element that is the container for this TextBox. How do I get that ItemsControl from my event handler?
Please note: Because the ItemsControl is in the DataTemplate of DataGrid, I don't believe I can just add an x:Name and reference it from my code-behind. Or is there a way to do that?
Thank you!
Using a combination of ItemsControl.ItemsControlFromItemContainer and VisualTreeHelper.GetParent you should be able to find your ItemsControl
var txt = sender as TextBox;
var panel1 = VisualTreeHelper.GetParent(txt);
var panel2 = VisualTreeHelper.GetParent(panel1);
var contentPresenter = VisualTreeHelper.GetParent(panel2);
var ic = ItemsControl.ItemsControlFromItemContainer(contentPresenter);
You may also want search the web for VisualTreeHelper Recursive functions to make some of this easier.
I like to have this little extension method in a static class somewhere in my app:-
public static IEnumerable<DependencyObject> Ancestors(this DependencyObject root)
{
DependencyObject current = VisualTreeHelper.GetParent(root);
while (current != null)
{
yield return current;
current = VisualTreeHelper.GetParent(current);
}
}
With that you should be able to do something like this:-
ItemsControl control = ((DependencyObject)sender).Ancestors()
.TypeOf<ItemsControl>().FirstOrDefault();
Not sure if this applies but this creates a "toggling button bar" using the same principles.
private void UIClassButton_Click(object sender, RoutedEventArgs e){
Button SenderButton = (Button)sender;
ItemsControl SendersItemControl = ItemsControl.ItemsControlFromItemContainer(VisualTreeHelper.GetParent(SenderButton));
IEnumerable<DependencyObject> DependencyObjectCollection = SendersItemControl.GetContainers();
foreach (ContentPresenter item in DependencyObjectCollection) {
ContentPresenter UIClassPresenter = (ContentPresenter)item;
Button UIClassButton = (Button)UIClassPresenter.GetVisualChildren().First();
if (UIClassButton != SenderButton) {
VisualStateManager.GoToState(UIClassButton, "Normal", true);
}
else {
VisualStateManager.GoToState(UIClassButton, "Pressed", true);
}
}
}
Here's an example of capturing a container that houses your ItemsControl's item:
CheckBox checkbox = sender as CheckBox;
foreach (var item in MembersItemsControl.Items)
{
var container = MembersItemsControl.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
UserInformation user = container.DataContext as UserInformation;
bool isMale = true;
if (user.sex == isMale && checkbox.IsChecked.Value == true)
{
container.Visibility = System.Windows.Visibility.Visible;
}
}
I hope that helps.

Resources