Awesomium WebControl WPF seems to misinterpret Scaling - wpf

I'm trying to build a simple prototype (that use Awesomium 1.7 RC3) that allows a WPF application to display a WebPage, allowing the page to be zoomed in / out. I don't want to preserve the layout, but adapt the layout in the same manner that you can resize the window. It's the same behavior as the zoom feature of Internet Explorer. The logical display area is raised while the actual rendering is zoomed.
For exemple, here is a screenshot of the app with a 100% zoom:
(source: hand-net.com)
The upper slider allows to control the zoom. When I change the zoom to 90% or 110%, here is what I get:
(source: hand-net.com)
And
(source: hand-net.com)
As you can see, the browser rendering is messy. Not only the internal rendering does not match the WebBrowser control area, but the picture resizing has a very low quality.
All of this were properly working with Awesomium 1.6.6.
How can I get the desired result?
The sample application can be downloaded here. The key parts are :
Xaml :
<Slider Value="{Binding Path=Zoom, Mode=TwoWay}"
IsSnapToTickEnabled="True"
TickPlacement="Both"
Grid.ColumnSpan="2"
Minimum="0.1"
Maximum="2.0"
TickFrequency="0.1"
LargeChange="0.1" />
<Grid x:Name="Container"
Background="SaddleBrown"
Grid.Row="1"
utils:SizeObserver.Observe="true"
utils:SizeObserver.ObservedWidth="{Binding ContainerActualWidth, Mode=OneWayToSource}"
utils:SizeObserver.ObservedHeight="{Binding ContainerActualHeight, Mode=OneWayToSource}">
<Grid x:Name="Containee"
Background="LightBlue"
RenderTransformOrigin="0,0"
Width="{Binding ContaineeWidth, Mode=OneWay}"
Height="{Binding ContaineeHeight, Mode=OneWay}">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding Zoom, Mode=OneWay}"
ScaleY="{Binding Zoom, Mode=OneWay}" />
<SkewTransform />
<RotateTransform />
<TranslateTransform />
</TransformGroup>
</Grid.LayoutTransform>
<awe:WebControl Source="http://www.flickr.com/search/?q=strasbourg&z=m" />
</Grid>
</Grid>
ViewModel that is used as DataContext:
public class ViewPortViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
Debug.WriteLine("Property changes: " + propertyName);
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private double m_Zoom = 1D;
public double Zoom
{
get { return m_Zoom; }
set
{
if (value != m_Zoom)
{
m_Zoom = value;
RaisePropertyChanged("Zoom");
RaisePropertyChanged("ContaineeWidth");
RaisePropertyChanged("ContaineeHeight");
}
}
}
private double m_ContainerActualWidth = 100D;
public double ContainerActualWidth
{
get { return m_ContainerActualWidth; }
set
{
if (value != m_ContainerActualWidth)
{
m_ContainerActualWidth = value;
RaisePropertyChanged("ContainerActualWidth");
RaisePropertyChanged("ContaineeWidth");
}
}
}
private double m_ContainerActualHeight = 100D;
public double ContainerActualHeight
{
get { return m_ContainerActualHeight; }
set
{
if (value != m_ContainerActualHeight)
{
m_ContainerActualHeight = value;
RaisePropertyChanged("ContainerActualHeight");
RaisePropertyChanged("ContaineeHeight");
}
}
}
public double ContaineeWidth
{
get { return m_ContainerActualWidth / Zoom; }
}
public double ContaineeHeight
{
get { return m_ContainerActualHeight / Zoom; }
}

That looks similar to a problem I had with a previous version of the control where text was rendered in an unreadable way. The solution (for me) was to set RenderOptions.BitmapScalingMode to NearestNeighbor in my XAML. The answer I got from Awesomium indicated that this was a known issue that they were developing several strategies for, while I haven't noticed the problem since then it may well have reoccurred.
Check the question Issue with blurry text and see if it solves your issue. This was what my XAML ended up looking like:
<awe:WebControl x:Name="MyBrowser"
Grid.Row="1"
Focusable="True"
Visibility="Visible"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SnapsToDevicePixels="True"
>
<awe:WebControl.Style>
<Style>
<Setter Property="RenderOptions.BitmapScalingMode" Value="NearestNeighbor" />
</Style>
</awe:WebControl.Style>
</awe:WebControl>

I found the solution in my awesomium support forum question, gave by Perikles.
Two steps to solve my issue:
Also apply layout tranform to the webcontrol :
<Grid x:Name="Containee"
Background="LightBlue"
RenderTransformOrigin="0,0"
Width="{Binding ContaineeWidth, Mode=OneWay}"
Height="{Binding ContaineeHeight, Mode=OneWay}">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding Zoom, Mode=OneWay}"
ScaleY="{Binding Zoom, Mode=OneWay}" />
</TransformGroup>
</Grid.LayoutTransform>
<awe:WebControl Source="http://www.flickr.com/search/?q=strasbourg&z=m"
LoadingFrameComplete="WebControl_LoadingFrameComplete_1">
<awe:WebControl.LayoutTransform>
<ScaleTransform ScaleX="{Binding Zoom, Mode=OneWay}"
ScaleY="{Binding Zoom, Mode=OneWay}" />
</awe:WebControl.LayoutTransform>
</awe:WebControl>
</Grid>
Subscribe to the LoadingFrameComplete event to apply rendering transform options after the page is loaded :
private bool renderingOptionsApplied;
private void WebControl_LoadingFrameComplete_1(object sender, Awesomium.Core.FrameEventArgs e)
{
if (renderingOptionsApplied)
return;
var webControl = sender as Awesomium.Windows.Controls.WebControl;
if ((webControl.Surface != null) && (webControl.Surface is WebViewPresenter))
{
RenderOptions.SetBitmapScalingMode(webControl.Surface as WebViewPresenter, BitmapScalingMode.Linear);
renderingOptionsApplied = true;
}
}

Related

WPF Popup positioning and drawn issues

I have a UserControl in which I create an Style that is applied later to the ContentControl of a Popup (below is all defined in the UserControl):
<Style x:Key="ttPopupContent" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<!-- BOTTOM Popup -->
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid x:Name="Grid">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle MinWidth="40" Fill="#fff" Stroke="#FF000000" Grid.Row="1" />
<StackPanel Grid.Row="1">
<TextBlock Text="My popup title for bottom popup" TextWrapping="Wrap"/>
<TextBlock Text="My popup body content for bottom popup" TextWrapping="Wrap" />
</StackPanel>
<Path Fill="#fff" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left"
Margin="{TemplateBinding Tag}" Width="20" Grid.Row="0" Data="M 0,21 L 10,0 20,21" />
<ContentPresenter Margin="8" Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Placement, ElementName=myPopup}" Value="Top">
<!-- TOP Popup -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid x:Name="Grid">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Rectangle MinWidth="40" Fill="#fff" Stroke="#FF000000" Grid.Row="0" />
<StackPanel Grid.Row="0">
<TextBlock Text="My popup title for top popup"
TextWrapping="Wrap"/>
<TextBlock Text="My popup body content for top popup"
TextWrapping="Wrap" />
</StackPanel>
<Path Fill="#fff" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left"
Margin="{TemplateBinding Tag}" Width="20" Grid.Row="1" Data="M 0,0 L 10,20 20,0" />
<ContentPresenter Margin="8" Grid.Row="0" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<!-- this label is placed at leftmost of the screen -->
<TextBlock x:Name="txtBlck1" Text="ShowInfo" />
<Popup x:Name="myPopup1" AllowsTransparency="True" Opened="Popup_Opened1"
PlacementTarget="txtBlck1" Placement="Bottom">
<ContentControl Style="{StaticResource ttPopupContent}"/>
</Popup>
<!-- this label is placed at rightmost of the screen -->
<TextBlock x:Name="txtBlck2" Text="AnotherLabel" />
<Popup x:Name="myPopup2" AllowsTransparency="True" Opened="Popup_Opened2"
PlacementTarget="txtBlck2" Placement="Bottom">
<ContentControl Style="{StaticResource ttPopupContent}"/>
</Popup>
Sometimes it does not put the arrow pointing correctly to the label at which popup is bound. For example, if I hover on the mouse on label "AnotherLabel" it is drawn as follows (this label is at the rightmost of the screen):
as you can see the arrow is not placed in the right place. However, I have another label "ShowInfo" that is placed at the leftmost of the screen, then it works:
So I am trying to adjust the arrow horizontal alignment to point correctly to the label by doing this in code-behind (xaml.cs):
private void Popup_Opened1(object sender, EventArgs e)
{
UIElement target = myPopup1.PlacementTarget;
Point adjust = target.TranslatePoint(new Point(8, 0), popup);
if (adjust.Y > 0)
{
popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Top;
}
myPopup1.Tag = new Thickness(adjust.X, -1.5, 0, -1.5);
}
private void Popup_Opened2(object sender, EventArgs e)
{
UIElement target = myPopup2.PlacementTarget;
Point adjust = target.TranslatePoint(new Point(8, 0), popup);
if (adjust.Y > 0)
{
popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Top;
}
myPopup2.Tag = new Thickness(adjust.X, -1.5, 0, -1.5);
}
What I am trying to do in code-behind is put the arrow in the correct place by adjusting it horizontally as explained here (in that case is a tooltip, in my case is a Popup).
I have two problems:
Adjusting horizontally the arrow to point correctly to the label.
The arrow is not drawn correctly, it appears a black line under. See below image:
You should extend Popup to conveniently implement the desired behavior.
I suggest to internally set the Popup.Placement to PlacementMode.Custom to enable custom positioning.
This gives you easy access to the positioning context in order to detect when the placement target is positioned right on the screen (in other words the Popup is right aligned with the parent Window.
The arrow box and the tool tip content should be hosted by a custom control.
To draw the arrow box properly you should create and draw geometry for example by overriding UIElement.OnRender of the custom control.
For simplicity, the following example only supports bottom right placement. You can follow the simple pattern to extend the features to support additional positioning (read comments in code):
public class MyToolTip : Popup
{
internal enum Side
{
None = 0,
Left,
Top,
Right,
Bottom
}
private MyToolTipContent ToolTipContent { get; }
static MyToolTip()
{
PlacementTargetProperty.OverrideMetadata(typeof(MyToolTip), new FrameworkPropertyMetadata(default(object), OnPlacementTargetChanged));
PlacementProperty.OverrideMetadata(typeof(MyToolTip), new FrameworkPropertyMetadata(null, CoercePlacement));
}
public MyToolTip()
{
this.ToolTipContent = new MyToolTipContent();
this.Child = this.ToolTipContent;
// Enable custom placement to get context related placement info
// like size and positions.
this.CustomPopupPlacementCallback = CalculatePosition;
this.Placement = PlacementMode.Custom;
this.AllowsTransparency = true;
}
private static void OnPlacementTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as MyToolTip).OnPlacementTargetChanged(e.OldValue as UIElement, e.NewValue as UIElement);
private static object CoercePlacement(DependencyObject d, object baseValue)
{
// Backup/delegate original PlacementMode as we need to override it
// in order to enable custom placement (and to keep it enabled by force).
(d as MyToolTip).ToolTipContent.Placement = (PlacementMode)baseValue;
// Enforce custom placement (invokation of the Popup.CustomPopupPlacementCallback)
return PlacementMode.Custom;
}
// Show Popup on mouse hover over the placement target
protected virtual void OnPlacementTargetChanged(UIElement oldTarget, UIElement newTarget)
{
if (oldTarget is not null)
{
newTarget.MouseEnter -= OnTargetMouseEnter;
newTarget.MouseLeave -= OnTargetMouseLeave;
}
if (newTarget is not null)
{
newTarget.MouseEnter += OnTargetMouseEnter;
newTarget.MouseLeave += OnTargetMouseLeave;
}
}
private void OnTargetMouseLeave(object sender, MouseEventArgs e) => this.IsOpen = false;
private void OnTargetMouseEnter(object sender, MouseEventArgs e) => this.IsOpen = true;
private CustomPopupPlacement[] CalculatePosition(Size popupSize, Size targetSize, Point offset)
{
Side clippingSide = GetParentWindowClippingSide(targetSize);
// TODO::Handle clipping of remaining sides (top and bottom. Left side will always fit).
switch (clippingSide)
{
// Popup will clip right Window bounds => shift Popup to the left to compensate
// (i.e. right align with parent Window).
case Side.Right:
AlignRightSide(popupSize, ref offset, targetSize);
break;
}
// TODO::Handle remaining modes
switch (this.ToolTipContent.Placement)
{
case PlacementMode.Bottom:
offset.Offset(0, targetSize.Height);
break;
case PlacementMode.Top:
break;
}
// Enforce OnRender to update the visual with the latest instructions
this.ToolTipContent.InvalidateVisual();
return new[] { new CustomPopupPlacement(offset, PopupPrimaryAxis.None) };
}
private void AlignRightSide(Size popupSize, ref Point offset, Size targetSize)
{
offset = new Point(targetSize.Width, 0);
offset.Offset(-popupSize.Width, 0);
this.ToolTipContent.HorizontalToolTipArrowAlignment = HorizontalAlignment.Right;
}
private Side GetParentWindowClippingSide(Size targetSize)
{
Window parentWindow = Window.GetWindow(this.PlacementTarget);
var parentWindowBounds = new Rect(new Point(), parentWindow.RenderSize);
Point targetPositionInParentWindow = this.PlacementTarget.TranslatePoint(new Point(), parentWindow);
var targetBounds = new Rect(targetPositionInParentWindow, targetSize);
/* Check for clipping */
bool isTargetRightSideInParentWindowBounds = parentWindowBounds.Contains(targetBounds.TopRight);
if (!isTargetRightSideInParentWindowBounds)
{
return Side.Right;
}
// TODO::Check if Popup clips top or bottom Window bounds following the above pattern. If it does, switch to Bottom/Top placement
// to make it fit the parent's bounds by changing the OriginalPlacementMode accordingly.
// If you don't want to overwrite the original value introduce a dedicated property
// that you really depend on (for example OriginalPlacementModeInternal).
// Popup completely fits the parent using the desired placement.
return Side.None;
}
}
MyToolTipContent.cs
The custom control to make up the actual content of the Popup, responsible for drawing the visual geometries.
internal class MyToolTipContent : Control
{
public PlacementMode Placement
{
get => (PlacementMode)GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
public static readonly DependencyProperty PlacementProperty = DependencyProperty.Register(
"Placement",
typeof(PlacementMode),
typeof(MyToolTipContent),
new PropertyMetadata(default));
private const double ArrowHeight = 12;
private const double ArrowWidth = 12;
internal HorizontalAlignment HorizontalToolTipArrowAlignment { get; set; }
static MyToolTipContent()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyToolTipContent), new FrameworkPropertyMetadata(typeof(MyToolTipContent)));
}
protected override void OnRender(DrawingContext drawingContext)
{
SolidColorBrush fillBrush = Brushes.Transparent;
var strokePen = new Pen(Brushes.Black, 2);
// Make room for the stroke
this.Margin = new Thickness(strokePen.Thickness);
var toolTipGeometry = new PathGeometry();
/* Create geometry depending on tool tip position.
*
* TODO::Draw different geometries based on different placement.
*
*/
if (this.HorizontalToolTipArrowAlignment == HorizontalAlignment.Right
&& this.Placement == PlacementMode.Bottom)
{
// "Push" content down to make room for the arrow
this.Padding = new Thickness(0, ArrowHeight, 0, 0);
var arrowBoxLines = new PolyLineSegment(new[]
{
new Point(this.RenderSize.Width - ArrowWidth, ArrowHeight),
new Point(this.RenderSize.Width - ArrowWidth / 2, 0),
new Point(this.RenderSize.Width, ArrowHeight),
new Point(this.RenderSize.Width, this.RenderSize.Height),
new Point(0, this.RenderSize.Height),
}, true);
var arrowBoxPath = new PathFigure(
new Point(0, ArrowHeight),
new[] { arrowBoxLines },
true);
toolTipGeometry.Figures.Add(arrowBoxPath);
}
drawingContext.DrawGeometry(fillBrush, strokePen, toolTipGeometry);
base.OnRender(drawingContext);
}
}
Generic.xaml
<Style TargetType="{x:Type local:MyToolTipContent}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<Border Padding="{TemplateBinding Padding}">
<StackPanel>
<TextBlock Text="My popup title for bottom popup"
TextWrapping="Wrap" />
<TextBlock Text="My popup body content for bottom popup"
TextWrapping="Wrap" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Wpf change xaml when object for binding changes

I am trying to get from my database one item, and when clicking on the next or previous button, I would like to get the next item out of my database by increasing its ID. I'm at the point of having my first item in my card, but when I click on previous or next, nothing happens.
I have in xaml:
<smtx:XamlDisplay Key="cards_1" Margin="4 4 0 0">
<materialDesign:Flipper Style="{StaticResource MaterialDesignCardFlipper}">
<materialDesign:Flipper.FrontContent>
<Grid Height="350" Width="200">
<Grid.RowDefinitions>
<RowDefinition Height="250" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<materialDesign:ColorZone Mode="PrimaryMid" VerticalAlignment="Stretch">
<materialDesign:PackIcon Kind="AccountCircle" Height="128" Width="128"
VerticalAlignment="Center" HorizontalAlignment="Center" />
</materialDesign:ColorZone>
<StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{Binding CurrentGebruiker.Naam}"></TextBlock>
<Button Style="{StaticResource MaterialDesignFlatButton}" Foreground="DarkGoldenrod"
Command="{x:Static materialDesign:Flipper.FlipCommand}"
Margin="0 4 0 0"
>SHOW DETAILS</Button>
</StackPanel>
</Grid>
</materialDesign:Flipper.FrontContent>
And my viewmodel:
public ZoekMatchViewModel()
{
LeesGebruiker(1);
KoppelenCommands();
}
private Gebruiker currentGebruiker;
public Gebruiker CurrentGebruiker
{
get
{
return currentGebruiker;
}
set
{
currentGebruiker = value;
NotifyPropertyChanged();
}
}
private void KoppelenCommands()
{
NextCommand = new BaseCommand(VolgendeGebruiker);
PrevCommand = new BaseCommand(VorigeGebruiker);
}
public ICommand NextCommand { get; set; }
public ICommand PrevCommand { get; set; }
private void LeesGebruiker(int id)
{
//instantiƫren dataservice
ZoekMatchDataService zoekMatchDS =
new ZoekMatchDataService();
currentGebruiker = zoekMatchDS.GetGebruiker(id);
}
public void VolgendeGebruiker()
{
if (CurrentGebruiker != null)
{
int id = (currentGebruiker.ID) + 1;
LeesGebruiker(id);
}
}
public void VorigeGebruiker()
{
if (CurrentGebruiker != null)
{
int id = (currentGebruiker.ID) - 1;
LeesGebruiker(id);
}
}
My buttons:
<Button Command="{Binding PrevCommand}" Background="Transparent" BorderThickness="0" Height="50">
and
<Button Command="{Binding NextCommand}" Background="Transparent" BorderThickness="0" Height="50">
So the problem is that my xaml doesn't update to the new user when I click on next or previous buttons.
If you need more information, I'm happy to provide!
I think the "right" way to do this would be with a custom Selector. Selector is the base class used in WPF for a control that lets the user select one or more items from a list. For example: ComboBox and ListBox are both Selectors. If you've never made a custom control before this may be a bit complicated though.
There are a few simpler answers. You could make a class that holds all the data you want to display for a single option, and have your Window define a property of that type. Then you could bind all the variable parts of your interface to that property and just change the property value manually when the user clicks the left or right button. Of course you'd only be able to use this on one place, whereas if you made a custom control you could reuse it anywhere, any number of times.

How can I refresh the position of a Slider with Scroll event?

I'm now tryin to scaling a image with mouse-wheel cmd.
and have some problems with refresh the position of slider position.
MainWindow.xaml
<Grid Grid.Row="1">
<Slider x:Name="slider_value" Value="{Binding Scale, Mode=TwoWay}" Maximum="3" Minimum="1"/>
</Grid>
<Other Grid>
<target obj want to scale>
<Grid.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=slider_value, Path=Value}"
ScaleY="{Binding ElementName=slider_value, Path=Value}"/>
</Grid.LayoutTransform>
</Other Grid>
MainViewModel.cs
private void ExecuteZoom(MouseWheelEventArgs e) {
if(e.Delta > 0) {
if(Scale>=3)
return;
Scale += 0.1;
}
if(e.Delta < 0) {
if(Scale<=1)
return;
Scale -= 0.1;
}
//((MainWindow)Application.Current.MainWindow).UpdateLayout();
//i thought this can be solved by above code but don't work
Debug.Print($"{Scale}");
}
I checked
the value of var Scale varies successfully with mouseWheel
the value of var Scale varies successfully with moving slider
the scale of target varies with slider moved by "Manually"
the value of var Scale successfully is changed
but the position of slider isn't affected, and also ScaleTransForm doesnt work.
thanks!
enter image description here
enter image description here

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

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.

Silverlight ItemsControl. Can you remove the panel completely via templating?

I have a DataTamplate for my ItemsControl that merely contains an Image with some other meta data. What I am trying to do is bind to the ItemsControl and have the Images be displayed with the Convas.Left and Canvas.Top that is bound via the data I give.
I have been trying my best to remove any Panels from the control via the ItemsPanelTemplate, so I can use the Attached Properties in the parent canvas, but it seems that you will always get a StackPanel by default.
Anyone out there have any good ideas?
Thanks,
Dave
The layout of items in an ItemsControl is controlled via the ItemsControl.ItemsPanel property which is of type ItemsPanelTemplate. The default value for the ItemsControl.ItemsPanel property is indeed an instance of ItemsPanelTemplate that specifies a StackPanel but this is completely customizable.
The code example (on this MSDN page) shown below the paragraph that starts "The following example creates an ItemsControl." is very useful in understanding what the ItemsControl.Template, ItemsControl.ItemsPanel and ItemsControl.ItemTemplate properties are for.
There are a few ways to achieve what you describe in the second sentence of the first paragraph of your question. Here is a full example:
Page.xaml:
<UserControl x:Class="ItemsControlImages.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:ItemsControlImages">
<UserControl.Resources>
<DataTemplate x:Key="CountryTemplate">
<Canvas>
<Image Canvas.Top="{Binding Location.Y}"
Canvas.Left="{Binding Location.X}"
Source="{Binding FlagImage}" />
<StackPanel Canvas.Top="{Binding Location.Y}"
Canvas.Left="{Binding Location.X}">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="{Binding Location}" />
<StackPanel.RenderTransform>
<TranslateTransform Y="-32.0" />
</StackPanel.RenderTransform>
</StackPanel>
</Canvas>
</DataTemplate>
</UserControl.Resources>
<Canvas x:Name="LayoutRoot">
<Canvas.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFB2C6D5"/>
<GradientStop Color="#FF1483D9" Offset="1"/>
</LinearGradientBrush>
</Canvas.Background>
<ItemsControl ItemTemplate="{StaticResource CountryTemplate}">
<app:Country Title="Argentina" Location="56,218" FlagImage="Images/ARG.png" />
<app:Country Title="China" Location="368,66" FlagImage="Images/CHN.png" />
<app:Country Title="Ireland" Location="192,90" FlagImage="Images/IRE.png" />
<app:Country Title="New Zealand" Location="404,225" FlagImage="Images/NZ.png" />
<app:Country Title="United States" Location="40,80" FlagImage="Images/USA.png" />
</ItemsControl>
</Canvas>
</UserControl>
Country.cs:
using System.ComponentModel;
using System.Windows;
namespace ItemsControlImages
{
public class Country : INotifyPropertyChanged
{
private string title;
private string flagImage;
private Point location;
public string Title
{
get
{
return this.title;
}
set
{
if ((object.ReferenceEquals(this.title, value) != true))
{
this.title = value;
this.RaisePropertyChanged("Title");
}
}
}
public string FlagImage
{
get
{
return this.flagImage;
}
set
{
if ((object.ReferenceEquals(this.flagImage, value) != true))
{
this.flagImage = value;
this.RaisePropertyChanged("FlagImage");
}
}
}
public Point Location
{
get
{
return this.location;
}
set
{
if ((this.location.Equals(value) != true))
{
this.location = value;
this.RaisePropertyChanged("Location");
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
That's all you need (along with the images in an Images folder) for this end result:
alt text http://www.freeimagehosting.net/uploads/bec683b75e.png
Note even though the Images are in an ItemsControl they are positioned at the coordinates shown by binding the Left and Top attached properties of their parent Canvas to the value of the X and Y coordinates from the custom Location property.
For more information on this sample and customizing the ItemsControl using templates in general you can check out this blog post.

Resources