Placing a PopUp to the top of the mouse-cursor position - wpf

I am working on an interactive chart, where I am displaying a popup with more information, when the user clicks on a data-point.This works fine so far and this is the popup definition:
<Popup IsOpen="{Binding PopupViewModel.IsOpen}"
Placement="Mouse"
HorizontalOffset="-150">
<Popup.Resources>
<DataTemplate DataType="{x:Type ViewModels:DataPointPopUpContentViewModel}">
<Views:DataPointPopUpContentView/>
</DataTemplate>
</Popup.Resources>
<Border BorderThickness="1" BorderBrush="Black" Background="White">
<ContentPresenter Content="{Binding PopupViewModel}" />
</Border>
</Popup>
The default placement of the popup, when using Placement="Mouse" is at the bottom right of the mouse-cursor. However I want the popup to be placed just at the top edge the mouse-cursor. As you can see I have achieved the horizontal centering by setting HorizontalOffset="-150", which is have of the fixed popup-width (Width=300). For the vertical placement I have the problem, that the popup-height is not fixed, since I am displaying an image of variable size and aspect-ratio inside it. I have therefore tried to set VerticalOffset to the ActualHeight of the pupup by adding VerticalOffset="{Binding ActualHeight}". This does not work unfortunately. Any ideas on what I am doing wrong and how to achieve my goal?

First of all you need a converter:
public class MultiplyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double && parameter is double)
{
return ((double)value) * ((double)parameter);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then try to bind the VerticalOffset property to the ActualHeight of the Popup's child:
<Window.Resources>
<local:MultiplyConverter x:Key="MultiplyConverter" />
<sys:Double x:Key="Factor">-.5</sys:Double>
</Window.Resources>
<Popup IsOpen="{Binding PopupViewModel.IsOpen}"
Placement="Mouse"
HorizontalOffset="-150">
<Popup.VerticalOffset>
<Binding Path="Child.ActualHeight" RelativeSource="{RelativeSource Self}"
Converter="{StaticResource MultiplyConverter}" ConverterParameter="{StaticResource Factor}" />
</Popup.VerticalOffset>
<!-- popup content -->
</Popup>
I hope it can help you.

Related

WPF binding to TextBox MinLines MaxLines

I have an wpf Expander with templated header. In this template, i have an TextBox, which use Binding with Converter to set MaxLines and MinLines, which depends on Expander.IsExpanded.
The idea is to let user see first line of text and show more when Expander is expanded (alternate solution would be to make that TextBox.Visiblitity = Collapsed when expanded and another TextBox.Visibility = Visible, but user will lost their cursor position, marked text and i dont know what else)
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="MyTemplate">
<Grid>
<Grid.Resources>
<Converters:ExpandedToLineRowsConverter ExpandedLines="5"
CollapsedLines="1"
x:Key="ExpandedToLines"/>
</Grid.Resources>
<TextBox MaxLines="{Binding IsExpanded,
Converter={StaticResource ExpandedToLines},
RelativeSource={RelativeSource FindAncestor,
AncestorLevel=1,
AncestorType={x:Type Expander}},
UpdateSourceTrigger=PropertyChanged}"
MinLines="{Binding IsExpanded,
Converter={StaticResource ExpandedToLines},
RelativeSource={RelativeSource FindAncestor,
AncestorLevel=1,
AncestorType={x:Type Expander}},
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<Expander Header="{Binding}"
HeaderTemplate="{StaticResource MyTemplate}">
<!-- other wpf controls under expander, they do not affect the problem -->
</Expander>
</UserControl>
ExpandedToLineRowsConverter is very simple:
public class ExpandedToLineRowsConverter : IValueConverter
{
public int ExpandedLines { get; set; }
public int CollapsedLines { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? ExpandedLines : CollapsedLines;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return (int)value != CollapsedLines;
}
}
Problem is, it is working only when expanding, not when collapsing (textbox stay in multiline mode even when Expander.IsExpanded = false).
When i set breakpoint in converter, it is returning corect number of lines, but it looks like the TextBox just ignore them.
I have no idea what to do...
Edit: sample VS2012 project with problem

WPF binding IValueConverter and the width of another control

The following code worked fine. There was an error elsewhere in the code. Still, the advice given is good.
I am trying to bind the Width of a TextBox to a percentage of the Width of a parent control. I know I can accomplish something similar by simply setting a Margin, but I was wondering why this doesn't work.
First, I set a reference to an IValueConverter in the resources collection of my user control:
<UserControl.Resources>
<local:TextBoxWidthConverter x:Key="txtWidthConv" />
</UserControl.Resources>
In the main xaml, I have the following:
<StackPanel Name="parentPanel" Width="300">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Hidden" Name="scroller" Width="{Binding Width,
ElementName=parentPanel, Converter={StaticResource txtWidthConv}}">
<StackPanel Orientation="Horizontal">
<TextBox></TextBox>
</StackPanel>
</ScrollViewer>
</StackPanel>
The ivalueconverter looks like this:
public class TextBoxWidthConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double result = (double)value;
if (!Double.IsNaN(result))
{
result = result * .25;
}
else
{
result = 100D;
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException("Not implemented.");
}
#endregion
}
Setting the width property does nothing here, let alone setting the IValueConverter. I would expect the ScrollViewer to be 1/4 the width of the parent StackPanel.
Set the ScrollViewer's HorizontalAlignment to something other than Stretch.
Also, you should bind to the ActualWidth property.
Let the layout system work for you instead of fighting it. Grid will automatically handle relative sizing:
<StackPanel Name="parentPanel" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Hidden" Name="scroller">
<StackPanel Orientation="Horizontal">
<TextBox></TextBox>
</StackPanel>
</ScrollViewer>
</Grid>
</StackPanel>

Expand/Collapse all expanders

I have a list of expanders which I want to control its expanded state(IsExpanded) with global toggle button which should toggle between expanded/collapsed state.
The solution which I have got so far does that by binding the expander's IsExpanded state to the togglebutton's IsChecked state. This works as long as I dont manually dont toggle the expanders. Once I do that those particular expanders dont respect the binding (toggle button's IsChecked state).
Any idea why? and is there a clean solution in XAML for this?
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<StackPanel>
<ToggleButton Name="ExpandAll">Toggle</ToggleButton>
<Expander IsExpanded="{Binding ElementName=ExpandAll,Path=IsChecked, Mode=OneWay}">
Hai
</Expander>
<Expander IsExpanded="{Binding ElementName=ExpandAll,Path=IsChecked, Mode=OneWay}">
Hello
</Expander>
<Expander IsExpanded="{Binding ElementName=ExpandAll,Path=IsChecked, Mode=OneWay}">
Weird
</Expander>
</StackPanel>
</Grid>
</Page>
I know that this post is very old. Just posting this for anyone else who comes across. The below code worked for me.
<Expander IsExpanded="{Binding ElementName=ExpandAll, Path=IsChecked, UpdateSourceTrigger=Explicit}">
</Expander>
This works when the expanders are generated dynamically, for example inside a DataGrid.RowDetailsTemplate.
I don't think you can achieve this entirely in XAML, but the following allows you to do it with an IValueConverter:
<StackPanel>
<StackPanel.Resources>
<local:Converter x:Key="Converter" />
</StackPanel.Resources>
<ToggleButton Name="ExpandAll">
<ToggleButton.IsChecked>
<MultiBinding Mode="OneWayToSource" Converter="{StaticResource Converter}">
<Binding ElementName="Expander1" Path="IsExpanded" />
<Binding ElementName="Expander2" Path="IsExpanded" />
<Binding ElementName="Expander3" Path="IsExpanded" />
</MultiBinding>
</ToggleButton.IsChecked>
Toggle</ToggleButton>
<Expander Name="Expander1">
Hai
</Expander>
<Expander Name="Expander2">
Hello
</Expander>
<Expander Name="Expander3">
Weird
</Expander>
</StackPanel>
And your Converter is as below:
public class Converter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//we're using OneWayToSource, so this will never be used.
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
// we want to convert the single 'IsChecked' value from the ToggleButton into
// 3 'IsExpanded' values
var allValues = new object[targetTypes.Length];
for (int i = 0; i < allValues.Length; i++)
{
allValues[i] = value;
}
return allValues;
}
}
This works by setting up a OneWayToSource binding between the IsChecked property of the ToggleButton (i.e. the binding will be set against the source when the target value changes), and uses an IMultiValueConverter to translate the single value into one for each of the Expanders.

Binding to Transforms in a ControlTemplate

I'm trying to create a custom control in Silverlight that dynamically scales an element in it's ControlTemplate. First attempt of the ControlTemplate looks something like this:
<ControlTemplate TargetType="controls:ProgressBar">
<Grid>
<Rectangle x:Name="TrackPart" Fill="{TemplateBinding Background}" HorizontalAlignment="Left" />
<Rectangle x:Name="ProgressPart" Fill="Blue" >
<Rectangle.RenderTransform>
<ScaleTransform ScaleX="{TemplateBinding Progress}" />
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</ControlTemplate>
However, this forum thread states that TemplateBinding only works on derivatives of FrameworkElements. ScaleTransform is not a FrameworkElement. Is there a work around for this? Any best practices for this sort of situation out there?
Rather than binding the ScaleX and ScaleY properties of the RenderTransform, you can bind the RenderTransform itself.
The problem is that the source is a double value, and you need a Transform. So you need to be able to convert a double to a ScaleTransform. You can create an IValueConverter to do that:
public class TransformConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double)
{
double d = (double)value;
return new ScaleTransform { ScaleY = d, ScaleX = d };
}
else
{
return new ScaleTransform();
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can't specify an IValueConverter to use in a TemplateBinding, so you can use a regular Binding with RelativeSource as TemplatedParent. Like this:
<Rectangle x:Name="ProgressPart" Fill="Blue"
RenderTransform="{Binding Path=Progress, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource converter1}}" >
and you need to place the IValueConverter in the resources of ControlTemplate's root, in scope of the Binding:
<ControlTemplate TargetType="controls:ProgressBar">
<Grid>
<Grid.Resources>
<local:TransformConverter x:Key="converter1" />
</Grid.Resources>
Assuming that you are always using simple items like a rectangle, you could bind the rectangle's height and width to the progress, and then use a binding converter to adjust the value accordingly

Show the validation error template on a different control in WPF

I have a UserControl that contains other controls and a TextBox. It has a Value property that is bound to the TextBox text and has ValidatesOnDataErrors set to True.
When a validation error occurs in the Value property binding, the error template (standard red border) is shown around the entire UserControl.
Is there a way to show it around the TextBox only?
I'd like to be able to use any error template so simply putting border around textbox and binding its color or something to Validation.HasError is not an option.
Here's my code:
<DataTemplate x:Key="TextFieldDataTemplate">
<c:TextField DisplayName="{Binding Name}" Value="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
</DataTemplate>
<controls:FieldBase x:Name="root">
<DockPanel DataContext="{Binding ElementName=root}">
<TextBlock Text="{Binding DisplayName}"/>
<TextBox x:Name="txtBox"
Text="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True}"
IsReadOnly="{Binding IsReadOnly}"/>
</DockPanel>
UserControl (FieldBase) is than bound to ModelView which performs validation.
to accomplish this task I've used this solution. It uses converter, that "hides" border by converting (Validation.Errors).CurrentItem to Thickness.
<Grid>
<Grid.Resources>
<data:ValidationBorderConverter
x:Key="ValidationBorderConverter" />
</Grid.Resources>
<Border
BorderBrush="#ff0000"
BorderThickness="{Binding
ElementName=myControl,
Path=(Validation.Errors).CurrentItem,
onverter={StaticResource ValidationBorderConverter}}">
<TextBox
ToolTip="{Binding
ElementName=myControl,
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Border>
</Grid>
ValidationBorderConverter class is pretty simple:
[ValueConversion(typeof(object), typeof(ValidationError))]
public sealed class ValidationBorderConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return (value == null) ? new Thickness(0) : new Thickness(1);
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Resources