Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Sounds like a job for the AdornerLayer to me.</p> <p>My implementation will just display one 'popup' at a time, and you can hide it by clicking the button another time. But you could also add a small close button to the ContactAdorner, or stick with your OK button, or fill the AdornerLayer behind the ContactAdorner with an element that IsHitTestVisible and reacts on click by hiding the open Adorner (so clicking anywhere outside closes the popup).</p> <p><em>Edit: Added the small close button at your request. Changes in ContactAdorner and the ContactDetailsTemplate.</em></p> <p>Another thing that you might want to add is repositioning of the adorner once it is clipped from the bottom (I only check for clipping from the right).</p> <p><img src="https://i.stack.imgur.com/KAAAk.png" alt="enter image description here"></p> <p>Xaml:</p> <pre><code>&lt;UserControl x:Class="WpfApplication1.ItemsControlAdorner" 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:i="http://schemas.microsoft.com/expression/2010/interactivity" mc:Ignorable="d" xmlns:local="clr-namespace:WpfApplication1" d:DesignHeight="300" d:DesignWidth="300"&gt; &lt;UserControl.DataContext&gt; &lt;local:ViewModel /&gt; &lt;/UserControl.DataContext&gt; &lt;UserControl.Resources&gt; &lt;local:EnumToBooleanConverter x:Key="EnumToBooleanConverter" /&gt; &lt;!-- Template for the Adorner --&gt; &lt;DataTemplate x:Key="ContactDetailsTemplate" DataType="{x:Type local:MyContact}" &gt; &lt;Border Background="#BBFFFFFF" BorderBrush="DarkOrchid" BorderThickness="1" CornerRadius="5" TextElement.Foreground="DarkOrchid" &gt; &lt;Grid Margin="5" Width="300"&gt; &lt;Grid.RowDefinitions&gt; &lt;RowDefinition Height="Auto" /&gt; &lt;RowDefinition Height="Auto" /&gt; &lt;RowDefinition Height="Auto" /&gt; &lt;RowDefinition Height="Auto" /&gt; &lt;/Grid.RowDefinitions&gt; &lt;Grid.ColumnDefinitions&gt; &lt;ColumnDefinition Width="*"/&gt; &lt;ColumnDefinition Width="*"/&gt; &lt;ColumnDefinition Width="Auto"/&gt; &lt;/Grid.ColumnDefinitions&gt; &lt;TextBlock Text="Full name" /&gt; &lt;TextBox Grid.Row="1" Text="{Binding FullName, UpdateSourceTrigger=PropertyChanged}" /&gt; &lt;TextBlock Grid.Row="2" Text="Address" /&gt; &lt;TextBox Grid.Row="3" Grid.ColumnSpan="2" Text="{Binding Address}" /&gt; &lt;TextBlock Grid.Column="1" Text="Gender" /&gt; &lt;StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="1" &gt; &lt;RadioButton Content="Male" IsChecked="{Binding Gender, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Gender.Male}}" /&gt; &lt;RadioButton Content="Female" IsChecked="{Binding Gender, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Gender.Female}}" /&gt; &lt;/StackPanel&gt; &lt;Button x:Name="PART_CloseButton" Grid.Column="2" Height="16"&gt; &lt;Button.Template&gt; &lt;ControlTemplate&gt; &lt;Border Background="#01FFFFFF" Padding="3" &gt; &lt;Path Stretch="Uniform" ClipToBounds="True" Stroke="DarkOrchid" StrokeThickness="2.5" Data="M 85.364473,6.9977109 6.0640998,86.29808 6.5333398,85.76586 M 6.9926698,7.4977169 86.293043,86.79809 85.760823,86.32885" /&gt; &lt;/Border&gt; &lt;/ControlTemplate&gt; &lt;/Button.Template&gt; &lt;/Button&gt; &lt;/Grid&gt; &lt;/Border&gt; &lt;/DataTemplate&gt; &lt;!-- Button/Item style --&gt; &lt;Style x:Key="ButtonStyle1" TargetType="{x:Type Button}" &gt; &lt;Setter Property="Foreground" Value="White" /&gt; &lt;Setter Property="FontFamily" Value="Times New Roman" /&gt; &lt;Setter Property="Background" Value="#CC99E6" /&gt; &lt;Setter Property="BorderThickness" Value="0" /&gt; &lt;Setter Property="MinHeight" Value="24" /&gt; &lt;Setter Property="Margin" Value="3,2" /&gt; &lt;Setter Property="Padding" Value="3,2" /&gt; &lt;Setter Property="Border.CornerRadius" Value="8" /&gt; &lt;Setter Property="Template"&gt; &lt;Setter.Value&gt; &lt;ControlTemplate TargetType="Button"&gt; &lt;Border CornerRadius="{TemplateBinding Border.CornerRadius}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" Margin="{TemplateBinding Margin}" &gt; &lt;ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" /&gt; &lt;/Border&gt; &lt;/ControlTemplate&gt; &lt;/Setter.Value&gt; &lt;/Setter&gt; &lt;/Style&gt; &lt;!-- ItemsControl style --&gt; &lt;Style x:Key="NamesStyle" TargetType="{x:Type ItemsControl}"&gt; &lt;Setter Property="ItemsPanel"&gt; &lt;Setter.Value&gt; &lt;ItemsPanelTemplate&gt; &lt;WrapPanel Orientation="Horizontal" /&gt; &lt;/ItemsPanelTemplate&gt; &lt;/Setter.Value&gt; &lt;/Setter&gt; &lt;Setter Property="ItemTemplate"&gt; &lt;Setter.Value&gt; &lt;DataTemplate&gt; &lt;Button x:Name="button" Style="{StaticResource ButtonStyle1}" Content="{Binding FullName}" &gt; &lt;i:Interaction.Behaviors&gt; &lt;local:ShowAdornerBehavior DataTemplate="{StaticResource ContactDetailsTemplate}" /&gt; &lt;/i:Interaction.Behaviors&gt; &lt;/Button&gt; &lt;/DataTemplate&gt; &lt;/Setter.Value&gt; &lt;/Setter&gt; &lt;/Style&gt; &lt;/UserControl.Resources&gt; &lt;Grid&gt; &lt;ItemsControl ItemsSource="{Binding MyContacts}" Style="{StaticResource NamesStyle}" /&gt; &lt;/Grid&gt; &lt;/UserControl&gt; </code></pre> <p>ShowAdornerBehavior, ContactAdorner, EnumToBooleanConverter:</p> <pre><code>using System.Windows; using System.Linq; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Interactivity; using System.Windows.Media; using System.Windows.Data; using System; namespace WpfApplication1 { public class ShowAdornerBehavior : Behavior&lt;Button&gt; { public DataTemplate DataTemplate { get; set; } protected override void OnAttached() { this.AssociatedObject.Click += AssociatedObject_Click; base.OnAttached(); } void AssociatedObject_Click(object sender, RoutedEventArgs e) { var adornerLayer = AdornerLayer.GetAdornerLayer(this.AssociatedObject); var contactAdorner = new ContactAdorner(this.AssociatedObject, adornerLayer, this.AssociatedObject.DataContext, this.DataTemplate); } } public class ContactAdorner : Adorner { private ContentPresenter _contentPresenter; private AdornerLayer _adornerLayer; private static Button _btn; private VisualCollection _visualChildren; private double _marginRight = 5; private double _adornerDistance = 5; private PointCollection _points; private static ContactAdorner _currentInstance; public ContactAdorner(Button adornedElement, AdornerLayer adornerLayer, object data, DataTemplate dataTemplate) : base(adornedElement) { if (_currentInstance != null) _currentInstance.Hide(); // hides other adorners of the same type if (_btn != null &amp;&amp; _btn == adornedElement) { _currentInstance.Hide(); // hides the adorner of this button (toggle) _btn = null; } else { _adornerLayer = adornerLayer; _btn = adornedElement; // adjust position if sizes change _adornerLayer.SizeChanged += (s, e) =&gt; { UpdatePosition(); }; _btn.SizeChanged += (s, e) =&gt; { UpdatePosition(); }; _contentPresenter = new ContentPresenter() { Content = data, ContentTemplate = dataTemplate }; // apply template explicitly: http://stackoverflow.com/questions/5679648/why-would-this-contenttemplate-findname-throw-an-invalidoperationexception-on _contentPresenter.ApplyTemplate(); // get close button from datatemplate Button closeBtn = _contentPresenter.ContentTemplate.FindName("PART_CloseButton", _contentPresenter) as Button; if (closeBtn != null) closeBtn.Click += (s, e) =&gt; { this.Hide(); _btn = null; }; _visualChildren = new VisualCollection(this); // this is needed for user interaction with the adorner layer _visualChildren.Add(_contentPresenter); _adornerLayer.Add(this); _currentInstance = this; UpdatePosition(); // position adorner } } /// &lt;summary&gt; /// Positioning is a bit fiddly. /// Also, this method is only dealing with the right clip, not yet with the bottom clip. /// &lt;/summary&gt; private void UpdatePosition() { double marginLeft = 0; _contentPresenter.Margin = new Thickness(marginLeft, 0, _marginRight, 0); // "reset" margin to get a good measure pass _contentPresenter.Measure(_adornerLayer.RenderSize); // measure the contentpresenter to get a DesiredSize var contentRect = new Rect(_contentPresenter.DesiredSize); double right = _btn.TranslatePoint(new Point(contentRect.Width, 0), _adornerLayer).X; // this does not work with the contentpresenter, so use _adornedElement if (right &gt; _adornerLayer.ActualWidth) // if adorner is clipped by right window border, move it to the left marginLeft = _adornerLayer.ActualWidth - right; _contentPresenter.Margin = new Thickness(marginLeft, _btn.ActualHeight + _adornerDistance, _marginRight, 0); // position adorner DrawArrow(); } private void DrawArrow() { Point bottomMiddleButton = new Point(_btn.ActualWidth / 2, _btn.ActualHeight - _btn.Margin.Bottom); Point topLeftAdorner = new Point(_btn.ActualWidth / 2 - 10, _contentPresenter.Margin.Top); Point topRightAdorner = new Point(_btn.ActualWidth / 2 + 10, _contentPresenter.Margin.Top); PointCollection points = new PointCollection(); points.Add(bottomMiddleButton); points.Add(topLeftAdorner); points.Add(topRightAdorner); _points = points; // actual drawing executed in OnRender } protected override void OnRender(DrawingContext drawingContext) { // Drawing the arrow StreamGeometry streamGeometry = new StreamGeometry(); using (StreamGeometryContext geometryContext = streamGeometry.Open()) { if (_points != null &amp;&amp; _points.Any()) { geometryContext.BeginFigure(_points[0], true, true); geometryContext.PolyLineTo(_points.Where(p =&gt; _points.IndexOf(p) &gt; 0).ToList(), true, true); } } // Draw the polygon visual drawingContext.DrawGeometry(Brushes.DarkOrchid, new Pen(_btn.Background, 0.5), streamGeometry); base.OnRender(drawingContext); } private void Hide() { _adornerLayer.Remove(this); } protected override Size MeasureOverride(Size constraint) { _contentPresenter.Measure(constraint); return _contentPresenter.DesiredSize; } protected override Size ArrangeOverride(Size finalSize) { _contentPresenter.Arrange(new Rect(finalSize)); return finalSize; } protected override Visual GetVisualChild(int index) { return _visualChildren[index]; } protected override int VisualChildrenCount { get { return _visualChildren.Count; } } } // http://stackoverflow.com/questions/397556/how-to-bind-radiobuttons-to-an-enum public class EnumToBooleanConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return value.Equals(parameter); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return value.Equals(true) ? parameter : Binding.DoNothing; } } } </code></pre> <p>ViewModel, MyContact:</p> <pre><code>using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Windows.Input; namespace WpfApplication1 { public class ViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } private ObservableCollection&lt;MyContact&gt; _myContacts = new ObservableCollection&lt;MyContact&gt;(); public ObservableCollection&lt;MyContact&gt; MyContacts { get { return _myContacts; } set { _myContacts = value; OnPropertyChanged("MyContacts"); } } public ViewModel() { MyContacts = new ObservableCollection&lt;MyContact&gt;() { new MyContact() { FullName = "Sigmund Freud", Gender = Gender.Male }, new MyContact() { FullName = "Abraham Lincoln", Gender = Gender.Male }, new MyContact() { FullName = "Joan Of Arc", Gender = Gender.Female }, new MyContact() { FullName = "Bob the Khann", Gender = Gender.Male, Address = "Mongolia" }, new MyContact() { FullName = "Freddy Mercury", Gender = Gender.Male }, new MyContact() { FullName = "Giordano Bruno", Gender = Gender.Male }, new MyContact() { FullName = "Socrates", Gender = Gender.Male }, new MyContact() { FullName = "Marie Curie", Gender = Gender.Female } }; } } public class MyContact : INotifyPropertyChanged { private string _fullName; public string FullName { get { return _fullName; } set { _fullName = value; OnPropertyChanged("FullName"); } } private string _address; public string Address { get { return _address; } set { _address = value; OnPropertyChanged("Address"); } } private Gender _gender; public Gender Gender { get { return _gender; } set { _gender = value; OnPropertyChanged("Gender"); } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public enum Gender { Male, Female } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload