Cannot apply animation to parent frame from page WPF c# - c#

I have a page within a Frame.What i am trying to do is to apply an storyboard/animation from the page to the parent Frame. Generally, from user controls, i used the following code to get the parent :
var parent = (Frame)this.Parent;
But if i use the same code in my page to get the parent frame and apply the animation :
private void Goback_MouseDown(object sender, MouseButtonEventArgs e)
{
Storyboard sb = new Storyboard;
sb = this.FindResource("HideMainframe");
var parent = (Frame)this.Parent;
Storyboard.SetTarget(sb, parent);
sb.Begin();
}
Storyboard
<Storyboard x:Key="HideMainframe" Storyboard.TargetProperty="Opacity" >
<DoubleAnimation Duration="0:0:0.5" To="0" >
<DoubleAnimation.EasingFunction>
<CircleEase EasingMode="EaseIn"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
i get an exception : No target was specified for 'System.Windows.Media.Animation.DoubleAnimation'..On the web,i came to know about VisualTreeHelper class but before i'ld go for that, i want to know,why my code is not working ? Or to be specific,why can't i get the parent frame from the page within ?

So it looks like Pages navigated in frames don't contain a "Parent" value, which is why the method you specified doesn't work, but walking the VisualTreeHelper does. What you are trying to achieve can be easily simplified by passing the Frame through the Page Constructor though.
Your Pages Code Behind
Frame f;
public Page1(Frame frame)
{
f = frame;
InitializeComponent();
}
Your button logic
private void Goback_MouseDown(object sender, MouseButtonEventArgs e)
{
Storyboard sb = (Storyboard)this.TryFindResource("HideMainframe");
Storyboard.SetTarget(sb, f);
sb.Begin();
}
So when navigating the frame, you'll just pass it through.
myFrame.Navigate(new Page1(myFrame));

Related

Inconsistent dispatcher.Invoke behavior

I have created a wallboard application for a service desk team, which uses WPF for front-end and the Cisco database of the phones in the back-end. The application is made of two screens that show different information, and these are displayed in the same screen and change between each other with a System.Timers.Timer.
The application is made so that if WindowA is visible, WindowB is shown and then WindowA is hidden. The moment one of the Windows becomes visible, that Window's timer become active again which resumes the database calls, while the other Window's timer becomes disabled:
private static void InterfaceChanger_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (WindowA.Visibility == Visibility.Visible)
{
WindowAEnabled = false;
ChangeVisibility(Visibility.Visible, WindowB);
WindowBEnabled = true;
WindowB_Elapsed(null, null); // force the call of the timer's callback
ChangeVisibility(Visibility.Collapsed, WindowA);
}
else
{
WindowBEnabled = false;
ChangeVisibility(Visibility.Visible, WindowA);
WindowAEnabled = true;
WindowA_Elapsed(null, null); // force the call of the timer's callback
ChangeVisibility(Visibility.Collapsed, WindowB);
}
}
private static void ChangeVisibility(Visibility visibility, Window window)
{
window.Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
{
window.Visibility = visibility;
}, null);
}
The problem is that this works perfectly... at most 90% of the time. The problem is that sometimes, if for example WindowA's visibility is changed to Visible and WindowB's visibility is changed to Collapsed, WindowB collapses but WindowA takes 2-3 seconds to become visible, while most times WindowA becomes visible and it's not seen when WindowB collapses. This (when it doesn't work) results in the Desktop being visible instead of the application.
I originally used DispatcherPriority.Background but that resulted in the screen changer working 70-80% of the time, so I decided to change it for DispatcherPriority.Normal (DispatcherPriority.Sendresults basically in the same situation as Normal).
Questions:
Is this the normal behavior to be expected by the Dispatcher, taking into account this is running in x64 mode in a quad-core CPU?
Knowing that the queries are performed in async methods not awaited, shouldn't the Dispatcher take priority over the methods?
Is there another way (without using the Dispatcher, or using another Window property) to accomplish what I'm looking for?
This is the code used to access/start the Windows:
//WindowA:
<Application x:Class="MyNamespace.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="WindowA.xaml">
//WindowA class:
public static WindowA WindowAInstance;
public WindowA()
{
// unnecessary code hidden
WindowAInstance = this;
WindowB b = new WindowB;
}
// WindowB class
public static WindowB WindowBInstance;
public WindowB()
{
// unnecessary code hidden
WindowBInstance = this;
}
// this is the code that starts the timers
public static void StartTimersHandling()
{
Database.RemoveAgents();
InterfaceChangerTimer = new System.Timers.Timer();
InterfaceChangerTimer.Interval = ApplicationArguments.InterfaceChangerTime;
InterfaceChangerTimer.Elapsed += InterfaceChanger_Elapsed;
InterfaceChangerTimer.AutoReset = true;
InterfaceChangerTimer.Start();
WindowATimer = new System.Timers.Timer();
WindowATimer.Interval = 1000;
WindowATimer.Elapsed += WindowATimer_Elapsed;
WindowATimer.AutoReset = true;
WindowATimer.Start();
WindowBTimer = new System.Timers.Timer();
WindowBTimer.Interval = 1000;
WindowBTimer.Elapsed += WindowBTimer_Elapsed;
WindowBTimer.AutoReset = true;
WindowBTimer.Start();
}
It sounds like you're writing a kiosk application (ie. full-screen, non-interactive). If this is the case I think you would be better off having a single window and switching the views inside it, rather than switching between two separate windows. Also, you need to separate the database query work from the refreshing of the window content. Furthermore, I think it would help if the views knew nothing about each other: at the moment your first window is tightly coupled to your second, which is not really a good idea.
In my opinion, if you changed your architecture a little, a lot of the problems you are having would disappear. Here's what I would recommend:
First, just go with a single window. Create two user controls (Project > Add User Control), and move your XAML layout from your existing windows into these two new controls. Then make your main window look something like this:
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:StackOverflow"
WindowState="Maximized" WindowStyle="None">
<Grid>
<my:UserControl1 x:Name="_first" Panel.ZIndex="1" />
<my:UserControl2 Panel.ZIndex="0" />
</Grid>
<Window.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard AutoReverse="True" RepeatBehavior="Forever">
<ObjectAnimationUsingKeyFrames BeginTime="0:0:5" Duration="0:0:5"
Storyboard.TargetName="_first"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
</Window>
This is a full-screen window with no chrome that contains your two user controls (essentially the contents of your existing windows). They are layered in a Grid element so that one sits on top of the other: I'm using the Panel.ZIndex property to force the first control to the top of the pile. Finally, I'm using an animation (triggered when the window loads) that toggles the visibility of one of the controls to hide it after a certain period of time. The animation is set to repeat and auto-reverse, the effect of which is to hide one of the controls, then make it visible again. You can change the Duration attribute value to control how long each control "stays" visible; it's set to 5 seconds in this example, which means a 10 second delay between switches.
The key to this working is that the first user control, when visible, must fully obscure the other user control that lies beneath it. This is easy to accomplish by setting the background colour of the control.
Your user controls can contain anything that a window would contain. Here's the example user control XAML that I used:
<UserControl x:Class="StackOverflow.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="White" Padding="40">
<TextBlock Text="{Binding Number}" FontSize="60"
TextAlignment="Center" VerticalAlignment="Top" />
</UserControl>
As you can see it's just a TextBlock element whose Text property binds to a Number property defined in the user control's code-behind. I used the same XAML for both user controls, just varying the VerticalAlignment of the text so that I could tell which control was visible at any given time.
The code-behind looks like this (it's the same for both, with the exception of the class name):
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Threading;
namespace StackOverflow
{
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public UserControl1()
{
InitializeComponent();
DataContext = this;
_timer = new DispatcherTimer
{ Interval = TimeSpan.FromSeconds(5), IsEnabled = true };
_timer.Tick += (sender, e) => Task.Run(async () => await DoWorkAsync());
}
readonly DispatcherTimer _timer;
readonly Random _random = new Random();
public event PropertyChangedEventHandler PropertyChanged;
public int Number
{
get
{
return _number;
}
private set
{
if (_number != value)
{
_number = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Number"));
}
}
}
}
int _number;
async Task DoWorkAsync()
{
// Asynchronous code started on a thread pool thread
Console.WriteLine(GetType().Name + " starting work");
_timer.IsEnabled = false;
try
{
await Task.Delay(TimeSpan.FromSeconds(_random.Next(4, 12)));
Number++;
}
finally
{
_timer.IsEnabled = true;
}
Console.WriteLine(GetType().Name + " finished work");
}
}
}
It basically contains a single Number property (which implements INotifyPropertyChanged) that gets incremented by a "worker" method. The worker method is invoked by a timer: here, I'm using a DispatcherTimer, but as I'm not changing any UI elements directly any of the .NET timers would have done.
The worker is scheduled to run on the thread pool using Task.Run, and then runs asynchronously. I'm simulating a long-running job by waiting for a period of time with Task.Delay. This worker method would be where your database query gets called from. You can vary the gap between successive queries by setting the timer's Interval property. There's nothing to say that the gap between queries need be the same as the refresh interval of your UI (ie. the speed at which the two views are switched); indeed, as your query takes a variable amount of time, syncing the two would be tricky anyway.
Try to use Dispatcher.CurrentDispatcher instead of window.Dispatcher and BeginInvoke:
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.DataBind, new Action(() =>
{
window.Visibility = visibility;
}));
Updated
Switch your timer to DispatcherTimer:
timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
timer.Tick += (sender, args) => InterfaceChanger_Elapsed();
timer.Start();

"Object reference not set to an instance of an object" hitting when loading a xaml page for the second time and more attempts

I am building stage wise in my augmented reality app. And I previously used a page named SearchLocation.xaml number of times to do a google geocoding search to find lat long of places.
But after I added more functionality to my app like the main AR page I am suspiciously hitting an exception while trying to search for a place's lat long through the SearchLocation.xaml page. It hits right after the InitializeComponent() method. To try and see what is causing this, I added a Loaded += SearchLocation_Loaded event and in the SearchLocation_Loaded event, I am setting the ContentPanel's visibility to visible(I hard coded its visibility to collapsed in xaml).
To my surprise, the exception hits everytime the ContentPanel shows up on screen. If I close the app completely and reopen it, it doesnt hit the first time, but when I try for the second time to open SearchLocation.xaml(which I linked to a tap event on a button in other page), I hit the exception. And the app closes.
I am completely clueless on what is causing this. Can anyone guide me in the right way so as to get rid of this exception ??
Code snippets will be provided if needed.
public SearchLocation()
{
InitializeComponent();
Loaded += SearchLocation_Loaded;
}
void SearchLocation_Loaded(object sender, RoutedEventArgs e)
{
ContentPanel.Visibility = Visibility.Visible;
}
The exception is throwing here because it is unhandled.
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
if (Debugger.IsAttached)
{
// An unhandled exception has occurred; break into the debugger
MessageBox.Show(e.ExceptionObject.Message);
Debugger.Break();
}
}
It happens whenever I open SearchLocation.xaml page through tapping a button from AddLocation.xaml for the second or third or any more further attempts.
I also used breakpoints to see at what line of code is the exception throwing, it is throwing right after everyline in public SearchLocation() {} has executed.
UPDATE: I managed to understand what is causing this exception. Below are two screenshots, one which is taken when the page opens for the first time. The other is when the page opens again. I saw two properties were set to null. Both of these are storyboard animations. How do I set back from null to their storyboard animations again ?
BEFORE:
AFTER:
As you can see, there is no change when the page loads for the first time and the second time. I am now totally clueless what to do.
This is the content in my InitializeComponent().
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Windows.Application.LoadComponent(this, new System.Uri("/FindIt;component/SearchLocation.xaml", System.UriKind.Relative));
this.HideTextBlock = ((System.Windows.Media.Animation.Storyboard)(this.FindName("HideTextBlock")));
this.UnhideTextBlock = ((System.Windows.Media.Animation.Storyboard)(this.FindName("UnhideTextBlock")));
this.LayoutRoot = ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
this.ContentPanel = ((System.Windows.Controls.Grid)(this.FindName("ContentPanel")));
this.HelpText = ((System.Windows.Controls.TextBlock)(this.FindName("HelpText")));
this.SearchText = ((System.Windows.Controls.TextBox)(this.FindName("SearchText")));
this.Search = ((System.Windows.Controls.Button)(this.FindName("Search")));
this.Add = ((System.Windows.Controls.Button)(this.FindName("Add")));
this.LocationMap = ((Microsoft.Phone.Maps.Controls.Map)(this.FindName("LocationMap")));
}
StoryBoard:
<phone:PhoneApplicationPage.Resources>
<Storyboard x:Name="HideTextBlock">
<DoubleAnimation
Duration="0:0:0.4"
From="120"
To="0"
Storyboard.TargetProperty="Height"
Storyboard.TargetName="HelpText"
d:IsOptimized="True" />
</Storyboard>
<Storyboard x:Name="UnhideTextBlock">
<DoubleAnimation
Duration="0:0:0.4"
From="0"
To="120"
Storyboard.TargetProperty="Height"
Storyboard.TargetName="HelpText"
d:IsOptimized="True" />
</Storyboard>
</phone:PhoneApplicationPage.Resources>
The method where storyboards are started:
private async void Search_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
HideTextBlock.Begin();
SystemTray.ProgressIndicator = new ProgressIndicator();
SetProgressIndicator(true);
SystemTray.ProgressIndicator.Text = "Searching";
string searchText = SearchText.Text;
HideTextBlock.Stop();
//Enforce string checking algorithms
searchText = searchText.Replace(" ", "+");
//http://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&sensor=true_or_false
HttpClient client = new HttpClient();
string url = "http://maps.googleapis.com/maps/api/geocode/json"+
"?address={0}"+
"&sensor=false";
string baseUrl = string.Format(url, searchText);
string googleResult = await client.GetStringAsync(baseUrl);
SystemTray.ProgressIndicator.Text = "Done";
SetProgressIndicator(false);
GoogleData apiData = JsonConvert.DeserializeObject<GoogleData>(googleResult);
if (apiData.status == "OK")
{
HelpText.Visibility = Visibility.Collapsed;
foreach (Result data in apiData.results)
{
latitude = data.geometry.location.lat;
longitude = data.geometry.location.lng;
foreach (AddressComponent addressData in data.address_components)
{
locationName = addressData.long_name;
break;
}
}
var mapCenter = new GeoCoordinate(latitude, longitude);
MapLayer myLayer = new MapLayer();
Pushpin pin = new Pushpin();
pin.Content = locationName;
//pin.GeoCoordinate = mapCenter;
pin.GeoCoordinate = mapCenter;
MapOverlay myOverlay = new MapOverlay();
myOverlay.Content = pin;
myOverlay.GeoCoordinate = mapCenter;
myOverlay.PositionOrigin = new Point(0, 1);
myLayer.Add(myOverlay);
LocationMap.Visibility = Visibility.Visible;
LocationMap.Layers.Add(myLayer);
LocationMap.Center = mapCenter;
LocationMap.ZoomLevel = 10;
}
else
MessageBox.Show("Please enter a place name to search");
UnhideTextBlock.Begin();
UnhideTextBlock.Stop();
}
Never mind. I did considerable amount of research and I found what is giving me this exception. There is an async task which is GetGeopositionAsync in the page from which I am navigating to SearchLocation.xaml.
If I wait in AddLocation.xaml page until the geoposition is acquired, it doesnt throw me any exception when I navigate to SearchLocation.xaml page.
So I need to issue a cancellation token whenever I navigate away from the page. And IT WORKS !

Reuse Storyboard in Windows Phone app

I've got a Storyboard used in my Windows Phone app:
<Canvas x:Name="myCanvas" Grid.Row="1">
<Canvas.Resources>
<Storyboard x:Name="sb">
<ColorAnimationUsingKeyFrames
Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
AutoReverse="True">
<EasingColorKeyFrame KeyTime="00:00:0" Value="Black" />
<EasingColorKeyFrame KeyTime="00:00:0.25" Value="Red" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Canvas.Resources>
</Canvas>
I've got multiple Rectangles that will be using this Storyboard, but it only seems to work the first time.
For example, the following code is used to display four different Rectangles but only the first one is displayed. The code does not error, but the last 3 Rectangles do not turn red and it appears the Storyboard doesn't even run.
sb.Stop();
sb.SetValue(Storyboard.TargetNameProperty, myRect1.name);
sb.Begin();
sb.Stop();
sb.SetValue(Storyboard.TargetNameProperty, myRect2.name);
sb.Begin();
sb.Stop();
sb.SetValue(Storyboard.TargetNameProperty, myRect3.name);
sb.Begin();
sb.Stop();
sb.SetValue(Storyboard.TargetNameProperty, myRect4.name);
sb.Begin();
Can anyone see what I'm doing wrong, or know how to get my Storyboard to be re-usable?
First, what you're doing wrong:
The storyboard execution is asynchronous. When you call the Storyboard.Begin method, the storyboard begins in background and your code continues to execute. Therefore, You're calling Storyboard.Stop right after starting it! The only one you don't stop is the last one, and that's why it's the only rectangle which color changes.
If you want to chain your animations, you have to subscribe to the Completed event to know when the storyboard ends, then restart it for the next controls. Here is one way of doing that:
private Rectangle[] ControlsToAnimate;
private int CurrentIndex;
private void Button_Click(object sender, RoutedEventArgs e)
{
this.ControlsToAnimate = new[] { this.Rectangle1, this.Rectangle2 };
this.Storyboard1.Completed += StoryboardCompleted;
this.AnimateNextControl();
}
private void AnimateNextControl()
{
if (this.CurrentIndex >= this.ControlsToAnimate.Length)
{
this.CurrentIndex = 0;
return;
}
var nextControl = this.ControlsToAnimate[this.CurrentIndex];
this.CurrentIndex++;
this.Storyboard1.Stop();
this.Storyboard1.SetValue(Storyboard.TargetNameProperty, nextControl.Name);
this.Storyboard1.Begin();
}
private void StoryboardCompleted(object sender, EventArgs e)
{
this.AnimateNextControl();
}
Now, you're going to face two problems:
When assigning the storyboard to a new control, the color of the previous control will return to its original value (before the storyboard was started). If you want it to keep its new value, you've got to save it:
private void AnimateNextControl()
{
if (this.CurrentIndex > 0)
{
var brush = (SolidColorBrush)this.ControlsToAnimate[this.CurrentIndex - 1].Fill;
brush.Color = brush.Color;
}
if (this.CurrentIndex >= this.ControlsToAnimate.Length)
{
this.CurrentIndex = 0;
return;
}
var nextControl = this.ControlsToAnimate[this.CurrentIndex];
this.CurrentIndex++;
this.Storyboard1.Stop();
this.Storyboard1.SetValue(Storyboard.TargetNameProperty, nextControl.Name);
this.Storyboard1.Begin();
}
You can't animate two controls at the same time using a single storyboard. If you want to animate all your rectangles at the same time, you need to use one storyboard per control.
Once again I must make my comment an answer since I still cannot comment apparently.
Are you attempting to run these all concurrently? I do believe that only a single instance of a XAML defined storyboard exists so you may not be able to reuse it on multiple controls at the same time.
If all else fails you can just create a UserControl with a rectangle containing the storyboard which you CAN reuse. That code sample I provided on your previous question is from a Tile that flips using a storyboard. Since its a usercontrol, I can have as many tiles as I want flipping at the same time.

Silverlight Canvas Sizing within a ScrollViewer

I am making a Silverlight 4 website with C#. In one of the pages, I want to have two panels beside each other. On the left is a map control and on the right is an image. That is easy enough, but I also want to be able to click on the image and leave PushPin like objects (like the map) so I put the image in a canvas and just draw circles. The problem is that image can be fairly large and I need to be able to scroll the image. I tried several different ways of achieving this, but so far no luck.
The answers to the following post seemed to be like the way to go, but there must be updates to Silverlight that broke it: WPF: How to make canvas auto-resize?
A similar solution suggested making the Canvas from scratch, but I ran into the same problem.
Most of my attempts end in displaying as much of the image as possible on the screen, but no scroll bars (still greyed out) or the page just goes white when the image is loaded.
The following is how I am currently selecting the image to load:
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "Image Files (*.png, *.jpg)|*.jpg;*.png";
if(dialog.ShowDialog() == true) {
BitmapImage bitmap = new BitmapImage();
FileStream stream = dialog.File.OpenRead();
bitmap.SetSource(stream);
TheImage.Source = bitmap;
}
There is probably be a nicer solution but this should do the trick.
I have created a small fixed size ScrollViewer that contains a Canvas and an image. I then used a behaviour to modify the size of the canvas to match the size of the image. The behaviour also handles the ImageOpened event to set the correct size of the image once the image is opened.
Here is the xaml:
<ScrollViewer Width="200" Height="200" HorizontalScrollBarVisibility="Auto">
<Canvas x:Name="TheCanvas">
<Image x:Name="TheImage">
<i:Interaction.Behaviors>
<Views:ResizeCanvasBehaviour Canvas="{Binding ElementName=TheCanvas}"/>
</i:Interaction.Behaviors>
</Image>
</Canvas>
</ScrollViewer>
Be sure to declare i as xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and b matches the namespace where you place the behaviour.
Here is the code for the behaviour:
public class ResizeCanvasBehaviour : Behavior<Image>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SizeChanged += AssociatedObject_SizeChanged;
AssociatedObject.ImageOpened += AssociatedObject_ImageOpened;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SizeChanged -= AssociatedObject_SizeChanged;
AssociatedObject.ImageOpened -= AssociatedObject_ImageOpened;
}
private void AssociatedObject_ImageOpened(object sender, RoutedEventArgs e)
{
BitmapSource bitmapSource = AssociatedObject.Source as BitmapSource;
if (bitmapSource == null)
{
return;
}
AssociatedObject.Width = bitmapSource.PixelWidth;
AssociatedObject.Height = bitmapSource.PixelHeight;
Resize();
}
private void AssociatedObject_SizeChanged(object sender, SizeChangedEventArgs e)
{
Resize();
}
public Canvas Canvas
{
get { return GetValue(CanvasProperty) as Canvas; }
set { SetValue(CanvasProperty, value); }
}
public static readonly DependencyProperty CanvasProperty = DependencyProperty.Register(
"Canvas",
typeof(Canvas),
typeof(ResizeCanvasBehaviour),
new PropertyMetadata(null, CanvasPropertyChanged));
private static void CanvasPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ResizeCanvasBehaviour)d).OnCanvasPropertyChanged();
}
private void OnCanvasPropertyChanged()
{
if (Canvas != null)
{
Resize();
}
}
private void Resize()
{
if ((AssociatedObject != null) && (Canvas != null))
{
Canvas.Width = AssociatedObject.ActualWidth;
Canvas.Height = AssociatedObject.ActualHeight;
}
}
}
To load the image do something like this. I did this in code behind for speed but ideally you should put this in a view model and then data bind the image Source property in xaml:
BitmapImage bi = new BitmapImage();
bi.UriSource = new Uri("http://farm7.static.flickr.com/6149/5942401995_a5a3fd3919_z.jpg");
TheImage.Source = bi;
Turns out the very minimum that I needed to do was set the Width and Height of the canvas to the PixelWidth and PixelHeight of the BitmapImage instance.
This is what Paul was doing with his solution (in a little more complicated way), but for some reason the resize event handlers would not get called when a image was loaded locally.
I had tried several different suggested solutions, but I never got the results I was wanting. This was the only solution that seemed to work.

Drag WPF Popup control

the WPF Popup control is nice, but somewhat limited in my opinion. is there a way to "drag" a popup around when it is opened (like with the DragMove() method of windows)?
can this be done without big problems or do i have to write a substitute for the popup class myself?
thanks
Here's a simple solution using a Thumb.
Subclass Popup in XAML and codebehind
Add a Thumb with width/height set to 0 (this could also be done in XAML)
Listen for MouseDown events on the Popup and raise the same event on the Thumb
Move popup on DragDelta
XAML:
<Popup x:Class="PopupTest.DraggablePopup" ...>
<Canvas x:Name="ContentCanvas">
</Canvas>
</Popup>
C#:
public partial class DraggablePopup : Popup
{
public DraggablePopup()
{
var thumb = new Thumb
{
Width = 0,
Height = 0,
};
ContentCanvas.Children.Add(thumb);
MouseDown += (sender, e) =>
{
thumb.RaiseEvent(e);
};
thumb.DragDelta += (sender, e) =>
{
HorizontalOffset += e.HorizontalChange;
VerticalOffset += e.VerticalChange;
};
}
}
There is no DragMove for PopUp. Just a small work around, there is lot of improvements you can add to this.
<Popup x:Name="pop" IsOpen="True" Height="200" Placement="AbsolutePoint" Width="200">
<Rectangle Stretch="Fill" Fill="Red"/>
</Popup>
In the code behind , add this mousemove event
pop.MouseMove += new MouseEventHandler(pop_MouseMove);
void pop_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
pop.PlacementRectangle = new Rect(new Point(e.GetPosition(this).X,
e.GetPosition(this).Y),new Point(200,200));
}
}
Building off of Jobi Joy's answer, I found a re-useable solution that allows you to add as a control within xaml of an existing control/page. Which was not possible adding as Xaml with a Name since it has a different scope.
[ContentProperty("Child")]
[DefaultEvent("Opened")]
[DefaultProperty("Child")]
[Localizability(LocalizationCategory.None)]
public class DraggablePopup : Popup
{
public DraggablePopup()
{
MouseDown += (sender, e) =>
{
Thumb.RaiseEvent(e);
};
Thumb.DragDelta += (sender, e) =>
{
HorizontalOffset += e.HorizontalChange;
VerticalOffset += e.VerticalChange;
};
}
/// <summary>
/// The original child added via Xaml
/// </summary>
public UIElement TrueChild { get; private set; }
public Thumb Thumb { get; private set; } = new Thumb
{
Width = 0,
Height = 0,
};
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
TrueChild = Child;
var surrogateChild = new StackPanel();
RemoveLogicalChild(TrueChild);
surrogateChild.Children.Add(Thumb);
surrogateChild.Children.Add(TrueChild);
AddLogicalChild(surrogateChild);
Child = surrogateChild;
}
}
Another way of achieving this is to set your Popup's placement to MousePoint. This makes the popup initially appear at the position of the mouse cursor.
Then you can either use a Thumb or MouseMove event to set the Popup's HorizontalOffset & VerticalOffset. These properties shift the Popup away from its original position as the user drags it.
Remember to reset HorizontalOffset and VerticalOffset back to zero for the next use of the popup!
The issue with loosing the mouse when moving too fast, could be resolved
This is taken from msdn:
The new window contains the Child content of Popup.
The Popup control maintains a reference to its Child content as a logical child. When the new window is created, the content of Popup becomes a visual child of the window and remains the logical child of Popup. Conversely, Popup remains the logical parent of its Child content.
In the other words, the child of the popup is displayed in standalone window.
So when trying to the following:
Popup.CaptureMouse() is capturing the wrapper window and not the popup itself. Instead using Popup.Child.CaptureMouse() captures the actual popup.
And all other events should be registered using Popup.Child.
Like Popup.Child.MouseMove, Popup.Child.LostCapture and so on
This has been tested and works perfectly fine
Contrary to what others have stated about this, I agree 100% with Jobi Joy's answer (which should honestly be the accepted answer). I saw a comment stating that the solution in the answer would cause memory fragmentation. This is not possible as creating new structs cannot cause memory fragmentation at all; in fact, using structs saves memory because they are stack-allocated. Furthermore, I think that this is actually the correct way to reposition a popup (after all, Microsoft added the PlacementRectangle property for a reason), so it is not a hack. Appending Thumbs and expecting a user to always place a Popup onto a canvas, however, is incredibly hacky and is not always a practical solution.
Private Point startPoint;
private void Window_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(null);
}
private void Window_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
Point relative = e.GetPosition(null);
Point AbsolutePos = new Point(relative.X + this.Left, relative.Y + this.Top);
this.Top = AbsolutePos.Y - startPoint.Y;
this.Left = AbsolutePos.X - startPoint.X;
}
}
This works for dragging my window, but like it was told if i move the mouse to fast, it would get out of window and stop raising the event. Without mentioning the dragging is not smooth at all. Does anyone knows how to do it properly, nice and smooth dragging, without loosing it when dragged too fast??? Post a simple example if possible, other than a whole tutorial that would get beginners like me lost in code. Thanks!

Categories

Resources