Silverlight Canvas Sizing within a ScrollViewer - c#

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.

Related

How to read image source or image name in xamarin.forms?

I am working on Xamarin forms, where I have used TapGestureRecognizer inside image now on tap of that image I need to get the source of the image in c#. How do I get that?. Why I am doing this is, radio buttons are not available in Xamarin forms, On tap of the image I will check the source of the image if the source is checked image then I need to change the source to unchecked and vice versa.
Here is my XAML code
<Image Scale="0.7" HorizontalOptions="Start" x:Name="radioButton" Source="unchecked.png">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="radioButton_Clicked">
</TapGestureRecognizer>
</Image.GestureRecognizers>
</Image>
Here is my C# code
private void radioButton_Clicked(object sender, EventArgs e)
{
var imageSource = "";//get image source here
if (imageSource == "Checked.png")
{
radioButton.Source = "Unchecked.png";
}
else
{
radioButton.Source = "Checked.png";
}
}
you can achieve like this on radioButton_Clicked action
private void radioButton_Clicked(object sender, EventArgs e)
{
var imageSource = (Image)sender;//get image source here
var selectedImage = imageSource.Source as FileImageSource;
if (selectedImage.File == "Checked.png")
{
radioButton.Source = "Unchecked.png";
}
else
{
radioButton.Source = "Checked.png";
}
}
or you can use the custom plugin for support checked box refer this
https://github.com/XLabs/Xamarin-Forms-Labs
you can cast sender to image control
var imageSender = (Image)sender;
gestures

How can I use a C# function to access the WPF control that called it?

I'm working on a game level editor in WPF using C#.
I have a series of image controls for choosing textures, and I want each image to be clickable, with some visible feedback to show which one is selected.
Here's one of the image controls, along with a green highlight border that shows up when it's clicked:
<Image x:Name="tile_image1" Source="as asphalt_test.png" Stretch="Fill" HorizontalAlignment="Right" VerticalAlignment="Top" Width="50" Height="50" MouseDown="texture_click" Margin="0,93,69,0" RenderTransformOrigin="0.16,2.04"/>
<Border x:Name="tile_border" BorderBrush="Lime" BorderThickness="3" HorizontalAlignment="Right" Height="54" Margin="0,91,65,0" VerticalAlignment="Top" Width="54" Visibility="Hidden" />
My question involves the "texture_click" function.
I want to re-use the same function for each image control, which I can easily assign using the MouseDown attribute in XAML. However, what I don't know is how to tell from within the function which control called it, or how to access that control's property's, such as ".Source". I want to be able to grab the file name of the image, as well as move the coordinates of the green border behind the new selection.
Right now, I just have it hard-coded to the first image control. Clicks on the other images will call the function, but the function will only select the first image (not the one that was actually clicked).
// click on tile 1
private void texture_click (object sender, MouseButtonEventArgs e)
{
tile_border.Visibility = Visibility.Visible;
current_tilefile = tile_image1.Source;
string source_string = Convert.ToString (tile_image1.Source);
int last_slash = source_string.LastIndexOf ('/');
current_tile = source_string.Substring (last_slash + 1, 3);
}
I tried using "sender", since I thought that might be the object that called the function, but that returned an error. I also tried calling the function with "texture_click (this)", but that was also no good. These were, admittedly, complete shots in the dark, so I wasn't surprised.
I'm still pretty new to this software, so any insight you guys can give would be great.
You just have to cast the sender parameter to the control type (Image in this case):
private void texture_click (object sender, MouseButtonEventArgs e)
{
//tile_border.Visibility = Visibility.Visible;
var image = sender as Image;
if (image != null)
{
current_tilefile = image.Source;
string source_string = image.Source.ToString();
int last_slash = source_string.LastIndexOf ('/');
current_tile = source_string.Substring (last_slash + 1, 3);
}
}
Of course, this doesn't give you access to the associated border. One thing you can do is to just dump the border into the Image's Tag property:
<Image x:Name="tile_image1" ... Tag="{Binding ElementName=tile_border}" />
<Border x:Name="tile_border" ... />
Then you can retrieve it, again by casting:
private void texture_click (object sender, MouseButtonEventArgs e)
{
var image = sender as Image;
if (image != null)
{
var border = image.Tag as Border;
if (border != null)
{
border.Visibility = Visibility.Visible;
}
// ...
}
}
Note that this (manipulating UI elements from code-behind) is not the ideal way to write a WPF application. Typically you would do something like this by using an existing control (like a ToggleButton), and re-writing its ControlTemplate so that its IsChecked visual state shows a border. But I realize that is a mouthful ...

change image.source when tapped

I'm a novice programmer so don't be brutal.
I'm making a game for Windows Store, and I want to animate a run cycle. I made many GIF animations but all have BLACK background, and I need it transparent. So I've decided to make a run cycle using DispatcherTimer. Everything works fine, but the images don't change :/
void timer_Tick(object sender, object e)
{
numer++;
if (numer > 8) numer = 1;
hero.Source.Equals("Assets/Anim/" + nazwa + numer + ".png");
}
Also, When I TAP a different image, it should change the image and other images, but it doesn't... what is wrong?
bool sun = true;
private void Image_Tapped(object sender, TappedRoutedEventArgs e)
{
sun = !sun;
if (sun == false)
{
Image1.Source.Equals("moon.png");
Image2.Source.Equals("ON.png");
}
else
{
Image1.Source.Equals("sun.png");
Image2.Source.Equals("OFF.png");
}
}
The xaml works fine, as the images are shown.
I have checked this question:
ImageTools on Windows Phone 8, changing ImageSource and DataContext
but I get loads of errors. I don't seem to understand how the property changed works.
it seems to be a small mistake. You are using the wrong method.
Image.Source.Equals()
is a boolean method that simply compares the current source with the "source" you give as arguement and will return true or false based on the comparison.
But what you want is to set the source of the image.
So you need to use:
Image1.Source = new BitmapImage(new Uri("moon.png", UriKind.RelativeOrAbsolute));
This will set the source of the Image to the new image you want.
Assuming that "moon.png" is in the main folder in your solution, the two solutions both work:
BitmapImage tn = new BitmapImage();
tn.SetSource(Application.GetResourceStream(new Uri(#"moon.png", UriKind.Relative)).Stream);
Image1.Source = tn;
Or
Image1.Source = new BitmapImage(new Uri("moon.png", UriKind.Relative));

Displaying a default image and changing it once the actual image finished loading

I have a WPF markup extension in charge of retrieving images by name, returning a BitmapImage object.
<Image Source="{my:ImageProvider ImageName=myImageName}"></Image>
Since retrieving an image is an operation that can possibly take a few seconds, I'd like to show a default image and display the requested image once it's ready.
What I tried to do is something like this, but as this may change the BitmapImage object, it won't update the UI (sample code):
BitmapImage img;
public override object ProvideValue(IServiceProvider serviceProvider)
{
img = new BitmapImage(new Uri(#"D:\defaultImage.png", UriKind.Absolute));
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
return img;
}
void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(5000);
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
img.UriSource = new Uri(#"D:\actualImage.png", UriKind.Absolute);
}
Is there a way I can update the UI to use the modified BitmapImage (something like INotifyPropertyChanged) or is there a different approach to achieve this?
PriorityBinding is what you might be looking for i guess.
You can bind to two different DP's with your actual image source as the highest binding and not forgot to set IsAsync property to true for that binding.
Once, your image source is ready it will automatically replaced the second binding.
Refer to this link to get started - http://msdn.microsoft.com/en-us/library/system.windows.data.prioritybinding.aspx
I ended up with two ways to do this. Both ways use a class that wraps the image and implements INotifyPropertyChanged:
class ImageSourceWrapper : ObservableObject
{
private ImageSource _image;
public ImageSource Image
{
get { return _image; }
set
{
if (value != _image)
{
_image = value;
RaiseOnPropertyChanged("Image");
}
}
}
public ImageSourceWrapper(ImageSource image)
{
Image = image;
}
}
First Approach
Once I have this, I can have my markup extension return an ImageSourceWrapper object and bind to it, like so
<Image Source="{Binding Source={my:ImageProvider ImageName=myImageName}, Path=Image}" />
I didn't really like this way, since it's pretty messy and involves having to know the ImageSourceWrapper class rather than working simply with ImageSource. Then I came up with the second approach.
Second Approach
In this approach I still use the ImageSourceWrapper class, but instead of having my markup extension return an ImageSourceWrapper object, I return a binding object which I set up to be bound to an ImageSourceWrapper object.
The markup extension looks something like this:
private ImageSourceWrapper _imageSourceWrapper;
public override object ProvideValue(IServiceProvider serviceProvider)
{
// Get the object and the property to be bound.
IProvideValueTarget service = IProvideValueTarget)provider.GetService(typeof(IProvideValueTarget));
DependencyObject targetObject = service.TargetObject as DependencyObject;
DependencyProperty targetProperty = service.TargetProperty as DependencyProperty;
// Set up the binding with the default image.
_imageSourceWrapper = new ImageSourceWrapper(DefaultImage);
Binding binding = new Binding("Image");
binding.Source = _imageSourceWrapper;
BindingOperations.SetBinding(targetObject, targetProperty, binding);
// Retrieve the actual image asynchronously.
GetImageAsync();
return binding.ProvideValue(serviceProvider);
}
private void GetImageAsync()
{
// Get the image asynchronously.
// Freeze the image so it could be accessed from all threads regardless
// of which thread it was created on.
newImage.Freeze();
// Got the image - update the _imageSourceWrapper object.
_imageSourceWrapper = newImage;
}
Then I can use it in XAML like this
<Image Source="{my:ImageProvider ImageName=myImageName}" />
This way the default image is displayed first, and once the requested image is retrieved, it will be displayed instead.
Sorry if the code here isn't entirely correct, I'm not near the code at the moment. Hopefully this is enough at the moment to express the main idea.

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