Please refer to the Model,ViewModel and View below.
The Textblock control on the view is not getting moved/updated to new position on the screen once i have set the value of the databinding property in the corresponding ViewModel.
Model:
private int _Text_1_Left;
public int Text_1_Left
{
get { return _Text_1_Left; }
set
{
if (_Text_1_Left != value)
{
_Text_1_Left = value;
}
}
}
ViewModel:
public class MyViewModel:INotifyPropertyChanged
{
private int _Text_1_Left = 50;
public int Text_1_Left
{
get { return _Text_1_Left; }
set
{
if (_Text_1_Left != value)
{
_Text_1_Left = value;
RaisePropertyChangedEvent("Text_1_Left");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChangedEvent(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
View(XAML):
<canvas>
<TextBlock Canvas.Left="{Binding ObjMyVM.Text_1_Left,Mode=OneWay}" Canvas.Top="10" />
</canvas>
View(Xaml.cs)
I am setting the DataBinding property in code behind of View.
public form_load()
{
MyViewModel objMyVM=new MyViewModel()
this.DataContext=objMyVM;
}
private void cmdMoveLeft_MouseUp(object sender, MouseButtonEventArgs e)
{
double Left;
TextBlock lbl_curr_selected;
lbl_curr_selected = Button_Text_1;
if (lbl_curr_selected != null)
{
Left = CurrentRec.Text_1_Left;
Left = Left <= 0 ? 0 : Left - 5;
ObjMyVM.Text_1_Left =int.Parse(Left.ToString()) ;
}
}
No error but textblock is supposed to move left by 5,which it is currently not doing.
Please show me how to move the position of a control element on the UI by changing the binding from code-behind.
As you have set the data context to the object your binding should just be:
Canvas.Left="{Binding Text_1_Left,Mode=OneWay}"
Check the console output for the error.
I would like to implement a popup style that looks like the following design:
The gray square represents the UIElement that shows the popup when it is clicked. The popup style is just a border (the easy part) with an arrow head that points to the center of the target element (the hard part). Also the aligment is important, when a control is placed on the right part of the window, the popup should be aligned to the right, otherwise to the left.
Is there an example or some documentation that instruct me how to proceed?
Okay, I have a solution for this. It's frustratingly complex.
If you're just after a plain pop-up, just with a tail, you can probably use chunks of this (ActualLayout and UpdateTail logic). If you're after the whole Help-Tip Experience™ you're in for an unpleasant ride.
I do think that it might be better to go down the Adorner route though (and I'm thinking of re-working this to use adorers). I've spotted some issues and it's still in work. Using pop-ups causes them to appear in the designer on the top of other windows and it's really annoying. I've also noticed they are not positioned correctly on some computers for some weird reasons (but none where I've got Visual Studio installed to properly debug).
It produces something like this:
With the following criteria:
Only a single help tip can be displayed on the screen at each time
If the user changes tab, and the control a help-tip is attached to is no longer visible, the help tip dissapears and the next help-tip is displayed
Once closed, a help-tip of that type won't be displayed again
Help tips can be turned off through one central option
Okay. So, the actual help tip is a usercontrol that's completely transparent and added to the UI. It has a pop-up that's managed using a static class. Here's the control:
<UserControl x:Class="...HelpPopup"
d:DesignHeight="0" d:DesignWidth="0">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</UserControl.Resources>
<Canvas>
<Popup x:Name="Popup"
d:DataContext="{d:DesignInstance {x:Null}}"
DataContext="{Binding HelpTip, ElementName=userControl}"
StaysOpen="True" PopupAnimation="Fade"
AllowsTransparency="True"
materialDesign:ShadowAssist.ShadowDepth="Depth3"
Placement="{Binding Placement, ElementName=userControl}"
HorizontalOffset="-10"
VerticalOffset="{Binding VerticalOffset, ElementName=userControl}">
<Grid Margin="0,0,0,0" SnapsToDevicePixels="True">
<Canvas Margin="10">
<local:RoundedCornersPolygon Fill="{StaticResource PrimaryHueDarkBrush}"
SnapsToDevicePixels="True"
ArcRoundness="4"
Points="{Binding PolygonPath, ElementName=userControl}"
Effect="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Popup}, Path=(materialDesign:ShadowAssist.ShadowDepth), Converter={x:Static converters:ShadowConverter.Instance}}"/>
</Canvas>
<Border BorderBrush="Transparent" BorderThickness="10,25,10,25">
<Grid x:Name="PopupChild">
<materialDesign:ColorZone Mode="PrimaryDark" Margin="5">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="AUTO"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Useful Tip"
FontWeight="Bold"
Margin="2,0,0,0"
Grid.ColumnSpan="2"
VerticalAlignment="Center"/>
<Button Style="{StaticResource MaterialDesignToolButton}" Click="CloseButton_Click" Grid.Column="1" Margin="0" Padding="0" Height="Auto">
<Button.Content>
<materialDesign:PackIcon Kind="CloseCircle" Height="20" Width="20" Foreground="{StaticResource PrimaryHueLightBrush}"/>
</Button.Content>
</Button>
</Grid>
<TextBlock Text="{Binding Message}"
TextWrapping="Wrap"
MaxWidth="300"
Margin="2,4,2,4"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Close" Padding="8,2" Height="Auto" Click="CloseButton_Click"
Margin="2"
Style="{StaticResource MaterialDesignFlatButtonInverted}"/>
<Button Content="Never show again"
Margin="2"
Padding="8,2"
Height="Auto"
Click="NeverShowButton_Click"
Style="{StaticResource MaterialDesignFlatButtonInverted}"/>
</StackPanel>
</StackPanel>
</materialDesign:ColorZone>
</Grid>
</Border>
</Grid>
</Popup>
</Canvas>
</UserControl>
You can change this to style how you want. I used a custom rounded polygon class and MaterialDesign colour zones. Replace these however you want.
Now, the code behind is... well, there's a lot of it, and it's not pleasant:
public enum ActualPlacement { TopLeft, TopRight, BottomLeft, BottomRight }
/// <summary>
/// Interaction logic for HelpPopup.xaml
/// </summary>
public partial class HelpPopup : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ActualPlacement actualPlacement = ActualPlacement.TopRight;
public ActualPlacement ActualPlacement
{
get { return actualPlacement; }
internal set
{
if (actualPlacement != value)
{
if (actualPlacement == ActualPlacement.BottomLeft || ActualPlacement == ActualPlacement.BottomRight)
{
Console.WriteLine("-10");
VerticalOffset = 10;
}
else if (actualPlacement == ActualPlacement.TopLeft || ActualPlacement == ActualPlacement.TopRight)
{
VerticalOffset = -10;
Console.WriteLine("10");
}
actualPlacement = value;
UpdateTailPath();
NotifyOfPropertyChange("ActualPlacement");
}
}
}
public void UpdateTailPath()
{
double height = PopupChild.ActualHeight + 30;
double width = PopupChild.ActualWidth;
switch (actualPlacement)
{
case ActualPlacement.TopRight:
polygonPath = "0.5,15.5 " + (width - 0.5) + ",15.5 " + (width - 0.5) + "," + (height - 15.5) +
" 15.5," + (height - 15.5) + " 0.5," + height + " 0.5,15.5"; ;
break;
case ActualPlacement.TopLeft:
polygonPath = "0.5,15.5 " + (width - 0.5) + ",15.5 " + (width - 0.5) + "," + height + " " + (width - 15.5) + "," + (height - 15.5) +
" 0.5," + (height - 15.5) + " 0.5,15.5";
break;
case ActualPlacement.BottomRight:
polygonPath = "0.5,0.5 15.5,15.5 " + (width - 0.5) + ",15.5 " + (width - 0.5) + "," + (height - 15.5) +
" 0.5," + (height - 15.5) + " 0.5,0.5";
break;
case ActualPlacement.BottomLeft:
polygonPath = "0.5,15.5 " + (width - 15.5) + ",15.5 " + (width - 0.5) + ",0.5 " + (width - 0.5) + "," + (height - 15.5) +
" 0.5," + (height - 15.5) + " 0.5,15.5";
break;
}
NotifyOfPropertyChange("PolygonPath");
}
private String polygonPath;
public String PolygonPath
{
get { return polygonPath; }
}
public PlacementMode Placement
{
get { return (PlacementMode)GetValue(PlacementProperty); }
set { SetValue(PlacementProperty, value); }
}
// Using a DependencyProperty as the backing store for Placement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlacementProperty =
DependencyProperty.Register("Placement", typeof(PlacementMode), typeof(HelpPopup), new PropertyMetadata(PlacementMode.Top));
public int VerticalOffset
{
get { return (int)GetValue(VerticalOffsetProperty); }
set { SetValue(VerticalOffsetProperty, value); }
}
// Using a DependencyProperty as the backing store for VerticalOffset. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.Register("VerticalOffset", typeof(int), typeof(HelpPopup), new PropertyMetadata(-10));
public HelpTip HelpTip
{
get { return (HelpTip)GetValue(HelpTipProperty); }
set { SetValue(HelpTipProperty, value); }
}
// Using a DependencyProperty as the backing store for Message. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HelpTipProperty =
DependencyProperty.Register("HelpTip", typeof(HelpTip), typeof(HelpPopup), new PropertyMetadata(new HelpTip() { Message = "No help message found..." }, HelpTipChanged));
private static void HelpTipChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((d as HelpPopup).HelpTipOnScreenInstance == null)
{
(d as HelpPopup).HelpTipOnScreenInstance = new HelpTipOnScreenInstance((d as HelpPopup));
}
(d as HelpPopup).HelpTipOnScreenInstance.HelpTip = (e.NewValue as HelpTip);
}
private static void HelpTipOnScreenInstance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
HelpTipOnScreenInstance htosi = sender as HelpTipOnScreenInstance;
if (e.PropertyName.Equals(nameof(htosi.IsOpen)))
{
//open manually to avoid stupid COM errors
if (htosi != null)
{
try
{
htosi.HelpPopup.Popup.IsOpen = htosi.IsOpen;
}
catch (System.ComponentModel.Win32Exception ex)
{
Canvas parent = htosi.HelpPopup.Popup.Parent as Canvas;
htosi.HelpPopup.Popup.IsOpen = false;
parent.Children.Remove(htosi.HelpPopup.Popup);
Application.Current.Dispatcher.BeginInvoke(new Action(() => {
htosi.HelpPopup.Popup.IsOpen = true;
parent.Children.Add(htosi.HelpPopup.Popup);
htosi.HelpPopup.UpdatePositions();
}), DispatcherPriority.SystemIdle);
}
}
}
}
private HelpTipOnScreenInstance helpTipOnScreenInstance;
public HelpTipOnScreenInstance HelpTipOnScreenInstance
{
get { return helpTipOnScreenInstance; }
set
{
if (helpTipOnScreenInstance != value)
{
if (helpTipOnScreenInstance != null)
{
HelpTipOnScreenInstance.PropertyChanged -= HelpTipOnScreenInstance_PropertyChanged;
}
helpTipOnScreenInstance = value;
HelpTipOnScreenInstance.PropertyChanged += HelpTipOnScreenInstance_PropertyChanged;
NotifyOfPropertyChange("HelpTipOnScreenInstance");
}
}
}
private double popupX;
public double PopupX
{
get { return popupX; }
set
{
if (popupX != value)
{
popupX = value;
NotifyOfPropertyChange("PopupX");
}
}
}
private double popupY;
public double PopupY
{
get { return popupY; }
set
{
if (popupY != value)
{
popupY = value;
NotifyOfPropertyChange("PopupY");
}
}
}
private void NotifyOfPropertyChange(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public HelpPopup()
{
InitializeComponent();
// Wire up the Loaded handler instead
this.Loaded += new RoutedEventHandler(View1_Loaded);
this.Unloaded += HelpPopup_Unloaded;
Popup.Opened += Popup_Opened;
//PopupChild.LayoutUpdated += HelpPopup_LayoutUpdated;
PopupChild.SizeChanged += HelpPopup_SizeChanged;
UpdatePositions();
}
private void Popup_Opened(object sender, EventArgs e)
{
UpdateTail();
UpdateTailPath();
}
private void HelpPopup_SizeChanged(object sender, SizeChangedEventArgs e)
{
Console.WriteLine(HelpTip.Message + ": " + e.PreviousSize.ToString() + " to " + e.NewSize.ToString());
UpdateTail();
UpdateTailPath();
}
private void HelpPopup_Unloaded(object sender, RoutedEventArgs e)
{
//don't waste resources on never show popups
if (HelpTip.NeverShow)
{
return;
}
HelpTipOnScreenInstance.IsOnscreen = false;
}
/// Provides a way to "dock" the Popup control to the Window
/// so that the popup "sticks" to the window while the window is dragged around.
void View1_Loaded(object sender, RoutedEventArgs e)
{
//don't waste resources on never show popups
if (HelpTip.NeverShow)
{
return;
}
//wait for a few seconds, then set this to on-screen
HelpTipOnScreenInstance.IsOnscreen = true;
//update so tail is facing right direction
UpdateTail();
Window w = Window.GetWindow(this);
// w should not be Null now!
if (null != w)
{
w.LocationChanged += delegate (object sender2, EventArgs args)
{
// "bump" the offset to cause the popup to reposition itself
// on its own
UpdatePositions();
};
// Also handle the window being resized (so the popup's position stays
// relative to its target element if the target element moves upon
// window resize)
w.SizeChanged += delegate (object sender3, SizeChangedEventArgs e2)
{
UpdatePositions();
};
}
}
private void UpdatePositions()
{
var offset = Popup.HorizontalOffset;
Popup.HorizontalOffset = offset + 1;
Popup.HorizontalOffset = offset;
UpdateTail();
}
private void UpdateTail()
{
UIElement container = VisualTreeHelper.GetParent(this) as UIElement;
Point relativeLocation = PopupChild.TranslatePoint(new Point(5, 5), container); //It HAS(!!!) to be this.Child
if (relativeLocation.Y < 0)
{
if (relativeLocation.X < -(PopupChild.ActualWidth-5 / 2))
{
ActualPlacement = ActualPlacement.TopLeft;
}
else
{
ActualPlacement = ActualPlacement.TopRight;
}
}
else
{
if (relativeLocation.X < -(PopupChild.ActualWidth-5 / 2))
{
ActualPlacement = ActualPlacement.BottomLeft;
}
else
{
ActualPlacement = ActualPlacement.BottomRight;
}
}
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
lock (HelpTip.Lock)
{
HelpTip.Closed = true;
HelpTipOnScreenInstance.IsOpen = false;
}
}
private void NeverShowButton_Click(object sender, RoutedEventArgs e)
{
lock (HelpTip.Lock)
{
HelpTip.Closed = true;
HelpTip.NeverShow = true;
HelpTipOnScreenInstance.IsOpen = false;
}
}
}
Things to note.
There's "ActualPlacement" to manage the actual placement of the popup, as setting placement is just an advisory to WPF.
UpdateTailPath() is re-drawing the polygon to get the tail in the
correct position after placement has changed.
We have both a HelpTip class that stores the information (title,
content etc), and HelpTipOnScreenInstance which controls whether it's
on screen. The reason for this is we can have multiple help-tip of
the same type on screen and only want to display one.
Various listeners for popup events to trigger tail updates.
We attach to the load and unload events of the usercontrol. This
allows us to keep track of whether the control is on the screen and
whether a help tip should or should not be displayed
(HelpTipOnScreenInstance.IsOnscreen = true).
We also listen for window change events so we can update the position
of the pop-up if the window is resized or moved.
Now, HelpTipOnScreenInstance, and HelpTip:
public class HelpTipOnScreenInstance : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public object Lock = new Object();
private void NotifyOfPropertyChange(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
//handler(this, new PropertyChangedEventArgs(propertyName));
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private HelpTip helpTip;
public HelpTip HelpTip
{
get { return helpTip; }
set
{
if (helpTip != value)
{
helpTip = value;
NotifyOfPropertyChange("HelpTip");
}
}
}
private bool isOpen = false;
public bool IsOpen
{
get { return isOpen; }
set
{
if (isOpen != value)
{
isOpen = value;
Console.WriteLine("Opening " + HelpTip.Message);
NotifyOfPropertyChange("IsOpen");
}
}
}
private bool isOnscreen = false;
public bool IsOnscreen
{
get { return isOnscreen; }
set
{
if (isOnscreen != value)
{
isOnscreen = value;
NotifyOfPropertyChange("IsOnscreen");
}
}
}
private HelpPopup helpPopup;
public HelpPopup HelpPopup
{
get { return helpPopup; }
set
{
if (helpPopup != value)
{
helpPopup = value;
NotifyOfPropertyChange("HelpPopup");
}
}
}
public HelpTipOnScreenInstance(HelpPopup helpPopup)
{
HelpPopup = helpPopup;
HelpTipManager.AddHelpTip(this);
}
}
public class HelpTip : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public object Lock = new Object();
private void NotifyOfPropertyChange(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
//handler(this, new PropertyChangedEventArgs(propertyName));
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private String id;
public String ID
{
get { return id; }
set { id = value; }
}
private String message;
public String Message
{
get { return message; }
set
{
if (message != value)
{
message = value;
NotifyOfPropertyChange("Message");
}
}
}
private bool closed;
public bool Closed
{
get { return closed; }
set
{
if (closed != value)
{
closed = value;
NotifyOfPropertyChange("Closed");
}
}
}
public bool NeverShow { get; set; }
}
And then a static manager class that keeps track of what's on screen and what's not, and chooses who get's displayed next:
public static class HelpTipManager
{
public static object Lock = new Object();
private static bool displayHelpTips = false;
public static bool DisplayHelpTips
{
get { return displayHelpTips; }
set {
if (displayHelpTips != value)
{
displayHelpTips = value;
if (displayHelpTips)
{
//open next!
OpenNext();
}
else
{
//stop displaying all
foreach(HelpTipOnScreenInstance helpTip in helpTipsOnScreen)
{
lock (helpTip.HelpTip.Lock)
{
helpTip.IsOpen = false;
}
}
}
}
}
}
private static List<HelpTipOnScreenInstance> helpTips = new List<HelpTipOnScreenInstance>();
private static List<HelpTipOnScreenInstance> helpTipsOnScreen = new List<HelpTipOnScreenInstance>();
private static bool supressOpenNext = false;
public static void AddHelpTip(HelpTipOnScreenInstance helpTip)
{
helpTip.PropertyChanged += HelpTip_PropertyChanged;
helpTips.Add(helpTip);
}
private static void HelpTip_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
HelpTipOnScreenInstance helpTip = sender as HelpTipOnScreenInstance;
if (helpTip != null)
{
//is this on screen or not?
switch (e.PropertyName)
{
case "IsOnscreen":
//Update our onscreen lists and perform related behaviour
if (helpTip.IsOnscreen)
{
AddedToScreen(helpTip);
}
else
{
RemovedFromScreen(helpTip);
}
break;
case "IsOpen":
lock (helpTip.Lock)
{
if (!supressOpenNext)
{
if (!helpTip.IsOpen)
{
OpenNext();
}
}
}
break;
}
}
}
private static void OpenNext()
{
if (DisplayHelpTips)
{
if (helpTipsOnScreen.Count > 0)
{
//check if none of them are open
if (helpTipsOnScreen.Count(ht => ht.IsOpen) == 0)
{
//open the first that's not been closed!
HelpTipOnScreenInstance firstNotClosed = helpTipsOnScreen.FirstOrDefault(ht => !ht.HelpTip.Closed);
if (firstNotClosed != null)
{
lock (firstNotClosed.Lock)
{
firstNotClosed.IsOpen = true;
}
}
}
}
}
}
private static void AddedToScreen(HelpTipOnScreenInstance helpTip)
{
lock (Lock)
{
helpTipsOnScreen.Add(helpTip);
OpenNext();
}
}
private static void RemovedFromScreen(HelpTipOnScreenInstance helpTip)
{
lock (Lock)
{
helpTipsOnScreen.Remove(helpTip);
supressOpenNext = true;
helpTip.IsOpen = false;
//OpenNext();
supressOpenNext = false;
}
}
}
So how to use it? You can add help tip data in your generic.xaml or a resource library like this:
<controls:HelpTip x:Key="KPIGraphMenu" ID="KPIGraphMenu" Message="Right click to change the colour, remove, or move KPI to view as a stacked trace. KPI can also be dragged onto other charts of any type."/>
and use them in the actual application like this, I like to overlay them in a grid with the control they're associated with, using the Alignment to determine where the tail points to:
<controls:HelpPopup HelpTip="{StaticResource KPIGraphMenu}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
I have used CustomPopupPlacementCallback Delegate . I have even considered vertical shifting of your arrow. So, now in this example below, arrow shifts left/right, up/down.
One can use this sample as it is.
Window1.xaml
<Window ...>
<Grid>
<Button Click="Btn_Click" Width="110" Height="25" Content="Button" HorizontalAlignment="Left" Margin="437,26,0,0" VerticalAlignment="Top"/>
<Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" Margin="10,90,0,0" VerticalAlignment="Top" Width="75"/>
<Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" Margin="139,146,0,0" VerticalAlignment="Top" Width="75"/>
<Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" Margin="180,0,0,0" VerticalAlignment="Top" Width="74"/>
<Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" Margin="224,333,0,0" VerticalAlignment="Top" Width="76"/>
<Button Click="Btn_Click" Content="Button" HorizontalAlignment="Right" VerticalAlignment="Top" Width="75"/>
<Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="75" />
<Button Click="Btn_Click" Content="Button" HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="75" />
<Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" />
<Popup x:Name="Popup1" Placement="Custom" StaysOpen="False" Opened="Popup1_Opened">
<Grid x:Name="Grd" Width="300" Height="100" Background="AliceBlue">
<Canvas x:Name="Cnv">
<Path x:Name="TopArrow" Canvas.Left="50" Canvas.Top="25" Margin="5" Data="M0,0 L-5,-5 L-10,0 z" Fill="Black" Stroke="Black" StrokeThickness="2"/>
<TextBlock Canvas.Top="35" FontSize="18" x:Name="Tb1"/>
</Canvas>
</Grid>
</Popup>
</Grid>
</Window>
Window1.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Shapes;
namespace ...
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Popup1.CustomPopupPlacementCallback =
new CustomPopupPlacementCallback(placePopup);
}
public CustomPopupPlacement[] placePopup(Size popupSize,
Size targetSize,
Point offset)
{
CustomPopupPlacement placement2 =
new CustomPopupPlacement(new Point(-(popupSize.Width - targetSize.Width / 2), targetSize.Height), PopupPrimaryAxis.Vertical);
CustomPopupPlacement placement1 =
new CustomPopupPlacement(new Point(targetSize.Width / 2, targetSize.Height), PopupPrimaryAxis.Vertical);
CustomPopupPlacement placement3 =
new CustomPopupPlacement(new Point(targetSize.Width/2, -popupSize.Height), PopupPrimaryAxis.Horizontal);
CustomPopupPlacement placement4 =
new CustomPopupPlacement(new Point(-(popupSize.Width - targetSize.Width/2), -popupSize.Height), PopupPrimaryAxis.Horizontal);
CustomPopupPlacement[] ttplaces =
new CustomPopupPlacement[] { placement1, placement2, placement3, placement4 };
return ttplaces;
}
private void Btn_Click(object sender, RoutedEventArgs e)
{
Popup1.PlacementTarget = sender as Button;
Popup1.IsOpen = true;
}
private void Popup1_Opened(object sender, EventArgs e)
{
Path arrow = ((Path)Popup1.FindName("TopArrow"));
Grid grd = ((Grid)Popup1.FindName("Grd"));
UIElement elem = (UIElement)Popup1.PlacementTarget;
Point elem_pos_lefttop = elem.PointToScreen(new Point(0, 0));
Point popup_pos_lefttop = grd.PointToScreen(new Point(0, 0));
if ( (elem_pos_lefttop.Y < popup_pos_lefttop.Y )
&&
((elem_pos_lefttop.X > popup_pos_lefttop.X))
)
{
Canvas.SetLeft(arrow, 280);
Canvas.SetTop(arrow, 25);
}
if ((elem_pos_lefttop.Y < popup_pos_lefttop.Y)
&&
((elem_pos_lefttop.X < popup_pos_lefttop.X))
)
{
Canvas.SetLeft(arrow, 30);
Canvas.SetTop(arrow, 25);
}
if ((elem_pos_lefttop.Y > popup_pos_lefttop.Y)
&&
((elem_pos_lefttop.X > popup_pos_lefttop.X))
)
{
Canvas.SetLeft(arrow, 280);
Canvas.SetTop(arrow, 90);
}
if ((elem_pos_lefttop.Y > popup_pos_lefttop.Y)
&&
((elem_pos_lefttop.X < popup_pos_lefttop.X))
)
{
Canvas.SetLeft(arrow, 30);
Canvas.SetTop(arrow, 90);
}
Tb1.Text = String.Format("Element = {0} \r\n Popup = {1}", elem_pos_lefttop, popup_pos_lefttop);
}
}
}
Please tell if this solves your issue.
i have a TextBox and a text hint that show as background:
but location of text as background which is not part of the Text of the base TextBox is not correctly.
xaml:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox HorizontalAlignment="Left" Height="23" Margin="152,19,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"
v:Autocomplete.Hint ="abcd"/>
</Grid>
Autocomplete.cs:
namespace WpfApplication2
{
public static class Autocomplete
{
#region Hint
public static string GetHint(DependencyObject obj)
{
return (string)obj.GetValue(HintProperty);
}
public static void SetHint(DependencyObject obj, string value)
{
obj.SetValue(HintProperty, value);
}
public static readonly DependencyProperty HintProperty =
DependencyProperty.RegisterAttached("Hint", typeof(string), typeof(Autocomplete), new PropertyMetadata(string.Empty, OnTextBoxBaseFocus));
private static string hintText = string.Empty;
private static void OnTextBoxBaseFocus(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBoxBase txtBase = (TextBoxBase)d;
hintText = GetHint(d);
if (txtBase == null)
return;
if ((string)e.NewValue != null && !GetHint(d).Contains(" "))
{
txtBase.GotFocus += txtBase_GotFocus;
txtBase.LostFocus += txtBase_LostFocus;
}
else
txtBase.TextChanged -= OnChanged;
}
static void txtBase_GotFocus(object sender, RoutedEventArgs e)
{
AutocompleteText(sender);
}
static void txtBase_LostFocus(object sender, RoutedEventArgs e)
{
TextBoxBase txtBase = (TextBoxBase)sender;
// Hide Autocomplete hint
txtBase.Background = null;
}
private static void OnChanged(object sender, TextChangedEventArgs e)
{
AutocompleteText(sender);
}
private static void AutocompleteText(object sender)
{
TextBoxBase txtBase = (TextBoxBase)sender;
if (txtBase != null && txtBase.Focus())
{
// Show Autocomplete hint
var visual = new TextBlock()
{
FontStyle = FontStyles.Normal,
Text = hintText,
Foreground = Brushes.Gray
};
txtBase.Background = new VisualBrush(visual)
{
Stretch = Stretch.None,
AlignmentX = AlignmentX.Left,
AlignmentY = AlignmentY.Center,
Transform = new TranslateTransform(3, 0)
};
}
else
{
// Hide Autocomplete hint
txtBase.Background = null;
}
}
#endregion
}
}
i expected:
How to show Autocomplete.Hint as expected?.Thanks for help me !
One way around this is to use a TextBox as your visual instead of TextBlock. This TextBox must have the same BorderThickness and the same Size as the original TextBoxBase. Therefore your AutocompleteText method should change like this:
private static void AutocompleteText(object sender)
{
TextBoxBase txtBase = (TextBoxBase)sender;
if (txtBase != null && txtBase.Focus())
{
// Show Autocomplete hint
var visual = new TextBox()
{
BorderThickness = txtBase.BorderThickness,
BorderBrush = Brushes.Transparent,
Width = txtBase.ActualWidth,
Height = txtBase.ActualHeight,
Text = hintText,
Foreground = Brushes.Gray
};
txtBase.Background = new VisualBrush(visual)
{
Stretch = Stretch.None,
};
}
else
{
// Hide Autocomplete hint
txtBase.Background = null;
}
}
I know you can achieve this in Silverlight 4 by playing with the ListBoxItem style's LayoutStates, i.e. BeforeUnloaded, BeforeLoaded and AfterLoaded.
It doesn't seem to be working at all in WP7 although these states exist in the default style.
I am currently using version 7.1.
Is there any way I can get this working?
Thanks,
Xin
for this I used Artefact Animator, it's for Silverlight but works perfectly for WP7 also. The code shows only the addition. Whole code from the project's sample page.
MainPage.xaml
<UserControl.Resources>
<!-- ADDS SMOOTH SCROLL -->
<ItemsPanelTemplate x:Key="ItemsPanelTemplate">
<StackPanel/>
</ItemsPanelTemplate>
</UserControl.Resources>
<Grid>
<ListBox x:Name="lb" Height="247" Width="100" ItemsPanel="{StaticResource ItemsPanelTemplate}" />
<Button x:Name="addBtn" Content="Add" Height="72" HorizontalAlignment="Left" Margin="159,145,0,0" VerticalAlignment="Top" Width="160" />
</Grid>
MainPage.xaml.cs
public partial class MainPage : PhoneApplicationPage
{
private static ScrollViewer _scrollViewer;
// Constructor
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// INIT
lb.Items.Clear();
lb.UpdateLayout();
// SCROLL INTERACTION
_scrollViewer = FindVisualChild<ScrollViewer>(lb);
var bar = FindVisualChild<ScrollBar>(_scrollViewer);
if (bar != null)
bar.ValueChanged += (s, args) => SetValue(ListBoxScrollOffsetProperty, args.NewValue);
// INPUT
addBtn.Click += (s, args) => AddItem();
}
private void AddItem()
{
// Create New ListBoxItem
var lbi = new ListBoxItem
{
Content = "Item " + lb.Items.Count,
RenderTransform = new CompositeTransform
{
TranslateX = -lb.Width
},
};
// Add ListBoxItem
lb.Items.Add(lbi);
lb.UpdateLayout();
// Animate In Item
ArtefactAnimator.AddEase(lbi.RenderTransform, CompositeTransform.TranslateXProperty, 0, 1, AnimationTransitions.CubicEaseOut, 0);
ArtefactAnimator.AddEase(this, ListBoxScrollOffsetProperty, _scrollViewer.ScrollableHeight, .8, AnimationTransitions.CubicEaseOut, 0);
}
// LISTBOX SCROLL OFFSET
public static readonly DependencyProperty ListBoxScrollOffsetProperty =
DependencyProperty.Register("ListBoxScrollOffset", typeof(double), typeof(MainPage), new PropertyMetadata(0.0, OnListBoxScrollOffsetChanged));
private static void OnListBoxScrollOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
_scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
public double ListBoxScrollOffset
{
get
{
return (double)GetValue(ListBoxScrollOffsetProperty);
}
set
{
SetValue(ListBoxScrollOffsetProperty, value);
}
}
// VISUAL HELPER
public static childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
{
return (childItem)child;
}
else
{
var childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
}
I created a custom password box user control which is able to show and hide the password. It just swaps out the standard password box with a textbox which is bound to the same password string property. It all works fine but now my data validation errors are no more shown, although they are being generated correctly in the background. Here's the xaml from my user control:
<UserControl x:Class="Controls.EAPPasswordBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" x:Name="_root">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top">
<PasswordBox x:Name="pwdBox" Password="{Binding Password, Mode=TwoWay,ValidatesOnDataErrors=True}" />
<TextBox x:Name="txtBox" Text="{Binding Password, Mode=TwoWay,ValidatesOnDataErrors=True}" />
</StackPanel>
</Grid>
Here's how I use it in a view:
<local:EAPPasswordBox x:Name="pwdBox"
Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="2" Password="{Binding password,Mode=TwoWay, ValidatesOnDataErrors=True}" ShowText="{Binding showPassword,Mode=TwoWay}"></local:EAPPasswordBox>
in the Parent view's viewmodel we implemented IDataErrorInfo like this:
public string this[string columnName]
{
get
{
string Result = "";
switch(columnName.ToLower())
{
case "password":
{
Result = Validatepassword();
break;
}
case "password2":
{
Result = Validatepassword2();
break;
}
default:
{
Result = this.ValidateStringValue(columnName);
break;
}
}
return Result;
}
}
Now when I enter text in the custom password box, the validation logic is called just fine but it's not displayed. Do I have to adjust my user control for this?
EDIT: Here's the code behind of my passwordbox:
public partial class EAPPasswordBox : UserControl, INotifyPropertyChanged
{
public bool ShowText
{
get { return (bool)GetValue(ShowTextProperty); }
set {
SetValue(ShowTextProperty, value);
if (value == true)
{
this.pwdBox.Visibility = System.Windows.Visibility.Collapsed;
this.txtBox.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.pwdBox.Visibility = System.Windows.Visibility.Visible;
this.txtBox.Visibility = System.Windows.Visibility.Collapsed;
}
}
}
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
private Visibility _PwdBoxVisibility;
public Visibility PwdBoxVisibility
{
get { return _PwdBoxVisibility; }
set
{
_PwdBoxVisibility = value; NotifyPropertyChanged("PwdBoxVisibility");
}
}
private Visibility _TxtBoxVisibility;
public Visibility TxtBoxVisibility
{
get { return _TxtBoxVisibility; }
set
{
_TxtBoxVisibility = value; NotifyPropertyChanged("TxtBoxVisibility");
}
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(string), typeof(EAPPasswordBox), null);
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register("ShowText", typeof(bool), typeof(EAPPasswordBox), new PropertyMetadata(OnShowTextPropertyChanged));
public EAPPasswordBox()
{
InitializeComponent();
this.pwdBox.SetBinding(PasswordBox.PasswordProperty, new System.Windows.Data.Binding() { Source = this, Path = new PropertyPath("Password"), Mode = BindingMode.TwoWay,ValidatesOnDataErrors=true });
this.txtBox.SetBinding(TextBox.TextProperty, new System.Windows.Data.Binding() { Source = this, Path = new PropertyPath("Password"), Mode = BindingMode.TwoWay, ValidatesOnDataErrors=true });
this.ShowText = false;
}
private static void OnShowTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
EAPPasswordBox passwordBox = d as EAPPasswordBox;
if (passwordBox != null)
{
passwordBox.ShowText=(bool)e.NewValue;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
2nd Edit: It would also help if someone would just explain to me the basics of binding properties of usercontrols in the xaml of a parent window/control. I dont quite understand why the usercontrol doesnt get the property changed events of the corresponding parent views viewmodel properties since it is bound to those via xaml.
Here's my solution at last. Since I realized that the DataContext of the user control automatically is the ViewModel of the parent view, I dumped the binding of the Password dependency property completely. I introduced a new parameter in the control which has to be set to the password property of the parent view model. I then use this string to do a manual binding of the textbox and the password box in the loaded event of the control. Here's my code:
public partial class EAPPasswordBox : UserControl, INotifyPropertyChanged
{
public bool ShowText
{
get { return (bool)GetValue(ShowTextProperty); }
set {
SetValue(ShowTextProperty, value);
if (value == true)
{
this.pwdBox.Visibility = System.Windows.Visibility.Collapsed;
this.txtBox.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.pwdBox.Visibility = System.Windows.Visibility.Visible;
this.txtBox.Visibility = System.Windows.Visibility.Collapsed;
}
}
}
public string PasswordPropertyName { get; set; }
private Visibility _PwdBoxVisibility;
public Visibility PwdBoxVisibility
{
get { return _PwdBoxVisibility; }
set
{
_PwdBoxVisibility = value; NotifyPropertyChanged("PwdBoxVisibility");
}
}
private Visibility _TxtBoxVisibility;
public Visibility TxtBoxVisibility
{
get { return _TxtBoxVisibility; }
set
{
_TxtBoxVisibility = value; NotifyPropertyChanged("TxtBoxVisibility");
}
}
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register("ShowText", typeof(bool), typeof(EAPPasswordBox), new PropertyMetadata(OnShowTextPropertyChanged));
public EAPPasswordBox()
{
InitializeComponent();
this.ShowText = false;
}
private static void OnShowTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
EAPPasswordBox passwordBox = d as EAPPasswordBox;
if (passwordBox != null)
{
passwordBox.ShowText=(bool)e.NewValue;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private void _root_Loaded(object sender, RoutedEventArgs e)
{
this.pwdBox.SetBinding(PasswordBox.PasswordProperty, new System.Windows.Data.Binding() { Source = this.DataContext, Path = new PropertyPath(PasswordPropertyName), Mode = BindingMode.TwoWay, ValidatesOnDataErrors = true });
this.txtBox.SetBinding(TextBox.TextProperty, new System.Windows.Data.Binding() { Source = this.DataContext, Path = new PropertyPath(PasswordPropertyName), Mode = BindingMode.TwoWay, ValidatesOnDataErrors = true });
}
}
Here's the XAML of the control.
<UserControl x:Class="GAB.EAP2011.Controls.EAPPasswordBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" x:Name="_root" Loaded="_root_Loaded">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top">
<PasswordBox x:Name="pwdBox" />
<TextBox x:Name="txtBox" />
</StackPanel>
</Grid>
Here's how to use it:
<local:EAPPasswordBox x:Name="pwdBox"
Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="2" PasswordPropertyName="password" ShowText="{Binding showPassword,Mode=TwoWay}"></local:EAPPasswordBox>
Now you got a nice password visibility switcher control :)
Comments appreciated!