I am creating a set of images dynamically and putting them into a Stack Panel like this :-
Image image = new Image();
image.Name = "image_" + iCounter;
image.Height = 100;
image.Width = 100;
image.Source = bitmap;
image.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
image.Stretch = Stretch.Fill;
image.VerticalAlignment = VerticalAlignment.Top;
//image.MouseDown += new MouseButtonEventHandler(image_MouseDown);
image.ToolTip = "Right-Click for Options";
image.ContextMenu = GetContextMenu();
Separator separator = new Separator();
separator.Name = "separator_" + iCounter;
AddImagesStackPanel.Children.Add(image);
AddImagesStackPanel.Children.Add(separator);
iCounter++;
Then in the Context Menu I have this code :-
private System.Windows.Controls.ContextMenu GetContextMenu()
{
System.Windows.Controls.MenuItem mi1;
System.Windows.Controls.MenuItem mi2;
System.Windows.Controls.ContextMenu _contextMenu = new System.Windows.Controls.ContextMenu();
mi1 = new System.Windows.Controls.MenuItem();
mi1.Header = "Show Normal Size";
mi1.Click += new RoutedEventHandler(ContextMenuItem1_Click);
mi2 = new System.Windows.Controls.MenuItem();
mi2.Header = "Remove image";
mi2.Click += new RoutedEventHandler(ContextMenuItem2_Click);
_contextMenu.Items.Add(mi1);
_contextMenu.Items.Add(mi2);
return _contextMenu;
}
Now I wish to get the selected item when the user right clicks on an image and I have this code :-
private void ContextMenuItem2_Click(object sender, RoutedEventArgs e)
{
object obj = e.OriginalSource;
string imageName = ((System.Windows.Controls.Image)obj).Name;
string[] split = imageName.Split('_');
imageUploads.RemoveAt(Convert.ToInt32(split[1]));
DisplayImagesInStackPanel(imageUploads);
}
But obj does not contain the name of the image since its a RoutedEventArgs. Is there any way I can get the selected item in the context menu?
After discussing this in the comments this should work:
// The binding source.
private readonly ObservableCollection<BitmapImage> _imageList = new ObservableCollection<BitmapImage>();
public ObservableCollection<BitmapImage> ImageList
{
get { return _imageList; }
}
How to display this and set up the ContextMenu:
<ItemsControl ItemsSource="{Binding ImageList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding}" Width="100" Height="100"
HorizontalAlignment="Left" Stretch="Fill"
VerticalAlignment="Top" ToolTip="Right-Click for Options">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Show Normal Size" Click="Image_CM_ShowNormalSize_Click"
Tag="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget}"/> <!-- The placement target is the object to which the context menu belongs, i.e. the image -->
<MenuItem Header="Remove Image" Click="Image_CM_RemoveImage_Click"
Tag="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext}"/> <!-- The DataContext of the Image is the BitmapImage, which should be removed from the list -->
</ContextMenu>
</Image.ContextMenu>
</Image>
<Separator/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
What the handlers might look like:
private void Image_CM_ShowNormalSize_Click(object sender, RoutedEventArgs e)
{
Image img = (sender as FrameworkElement).Tag as Image;
img.Width = (img.Source as BitmapImage).PixelWidth;
img.Height = (img.Source as BitmapImage).PixelHeight;
}
private void Image_CM_RemoveImage_Click(object sender, RoutedEventArgs e)
{
BitmapImage img = (sender as FrameworkElement).Tag as BitmapImage;
// If the image is removed from the bound list the respective visual elements
// will automatically be removed as well.
ImageList.Remove(img);
}
But obj does not contain the name of the image since its a RoutedEventArgs.
True, but the obj at that point is a MenuItem, if you drill one level down, you can get the image.
Is there any way I can get the selected item in the context menu?
Normally one would load the model classes (Image in your case) through the binding of ItemSource of the Menu (or MenuItem if they are to be submenus) and if one takes that route they can pull the originating item off of the DataContext such as in my case the item was an MRU class item.
private void SelectMRU(object sender, RoutedEventArgs e)
{
var mru = (e.OriginalSource as MenuItem).DataContext as MRU;
var name = mru.Name;
...
}
Because you do things by hand you should load the Tag property of the MenuItem with the Image in question
mi1.Tag = {Image instance in question};
then extract on the event.
var image = (e.OriginalSource as MenuItem).Tag as Image;
Related
I'm trying to create a snapshot/image/bitmap of an element that I can display as content in another control.
It seems the suggested way to do this is with a VisualBrush, but I can't seem to get it to create a snapshot of the current value and keep that state. When you alter the original source, the changes are applied to all the "copies" that have been made too.
I have made a simple example to show what I mean.
What I want is for the items added to the stackpanel to have the opacity that was set when they were cloned. But instead, changing the opacity on the source changes all "clones".
<StackPanel Width="200" x:Name="sp">
<DockPanel>
<Button Content="Clone"
Click="OnCloneButtonClick" />
<TextBlock Text="Value" x:Name="tb" Background="Red" />
</DockPanel>
</StackPanel>
private void OnCloneButtonClick(object sender, RoutedEventArgs e)
{
tb.Opacity -= 0.1;
var brush = new VisualBrush(tb).CloneCurrentValue();
sp.Children.Add(new Border() { Background = brush, Width = tb.ActualWidth, Height = tb.ActualHeight });
}
I am afraid the visual elements aren't cloned when you call CloneCurrentValue().
You will have to clone the element yourself, for example by serializing the element to XAML and then deserialize it back using the XamlWriter.Save and XamlReader.Parse methods respectively:
private void OnCloneButtonClick(object sender, RoutedEventArgs e)
{
tb.Opacity -= 0.1;
var brush = new VisualBrush(Clone(tb));
sp.Children.Add(new Border() { Background = brush, Width = tb.ActualWidth, Height = tb.ActualHeight });
}
private static Visual Clone(Visual visual)
{
string xaml = XamlWriter.Save(visual);
return (Visual)XamlReader.Parse(xaml);
}
I have large treeview full of textboxes, each with tooltip containing a unique image. The image is stored in a property as a bytearray and I bind to it. Every time a new tooltip is displayed more memory is used.
I will be scaling the image, but that doesn't address the root of the problem. If there a way to free the memory used after the tooltip is no longer displayed?
<TextBlock.ToolTip>
<StackPanel>
<Image MaxWidth="650"
MaxHeight="400"
Source="{Binding ImageAsByteArray}"/>
<TextBlock Text="{Binding FilePath, StringFormat='Full Path: {0}'}" />
</StackPanel>
</TextBlock.ToolTip>
The tooltip can be set to any framework element, so could dynamically create this as an object behind the scenes:
<TextBlock ToolTip={Binding ToolTip} />
Then your view model or code behind could dynamically create this object and handle the loaded/unloaded event to capture when the tooltip displays.
I've done it below with a canvas is case you want to add other children besides the image:
var tooltipCanvas = new Canvas();
var img = new Image();
tooltipCanvas.Children.Add(img);
tooltipCanvas.Width = 500;
tooltipCanvas.Height = 500;
tooltipCanvas.Loaded += Tooltip_Loaded;
tooltipCanvas.Unloaded += Tooltip_Unloaded;
Then you could populate the image source just for the time the image is shown using the loaded and unloaded event handlers:
private void Tooltip_Loaded(object sender, RoutedEventArgs e)
{
var canvas = sender as Canvas;
var img = canvas.Children[0] as Image;
img.Source = /* get your image bytes */;
}
private void Tooltip_Unloaded(object sender, RoutedEventArgs e)
{
var canvas = sender as Canvas;
var img = canvas.Children[0] as Image;
img.Source = null;
}
Title just about says it all.I have a lot of buttons, all of them have the same context menu, I want to determine from the click event which button was rgiht clicked to get there.
This code does not work, placementTarget is null:
private void mi_Click(object sender, RoutedEventArgs e)
{
Button contextMenuEzen = null;
MenuItem mnu = sender as MenuItem;
if (mnu != null)
{
ContextMenu ize =(ContextMenu)mnu.Parent;
contextMenuEzen = ize.PlacementTarget as Button;
}
}
Please help me!
For me, this example works:
XAML
<Window.Resources>
<!-- For all MenuItems set the handler -->
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Click" Handler="MenuItem_Click" />
</Style>
<!-- One ContextMenu for all buttons (resource) -->
<ContextMenu x:Key="MyContextMenu">
<MenuItem Header="Click this" />
<MenuItem Header="Two" />
<MenuItem Header="Three" />
</ContextMenu>
</Window.Resources>
<Grid>
<Button x:Name="MyButton1" Width="100" Height="30" Content="MyButton1" ContextMenu="{StaticResource MyContextMenu}" />
<Button x:Name="MyButton2" Margin="0,110,0,0" Width="100" Height="30" Content="MyButton2" ContextMenu="{StaticResource MyContextMenu}" />
</Grid>
Code behind
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mnu = sender as MenuItem;
Button MyButton = null;
if (mnu != null)
{
ContextMenu MyContextMenu = (ContextMenu)mnu.Parent;
MyButton = MyContextMenu.PlacementTarget as Button;
}
MessageBox.Show(MyButton.Content.ToString());
}
RoutedEvents don't work exactly like regular events - the signature of the handler is EventHandler(object sender, RoutedEventArgs e). The RoutedEventArgs has a property called OriginalSource that represents the element in your UI that was actually clicked. In contrast, the sender parameter will always be the object on which the event handler is registered.
use the ContextMenuService to get the placement target as in the following example:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += (sender, args) => {
RoutedEventHandler clickHandler = delegate(object o, RoutedEventArgs eventArgs) {
var mi = (MenuItem) o;
var contextMenu = (ContextMenu) mi.Parent;
var pTarget = ContextMenuService.GetPlacementTarget(contextMenu) as Button;
// just to make sure
if (pTarget == null) {
return;
}
string message = "You clicked on the button " + pTarget.Content;
MessageBox.Show(message);
};
// create a single instance of the ContextMenu
var cm = new ContextMenu();
for (int i = 0; i < 10; i++) {
var mi = new MenuItem {Header = "Item " + i};
mi.Click += clickHandler;
cm.Items.Add(mi);
}
// create a set of buttons and assign them to the RootVisual(StackPanel)
for (int i = 0; i < 5; i++) {
var button = new Button {Content = "Button " + i, ContextMenu = cm};
this.RootVisual.Children.Add(button);
}
};
}
}
I have a WPF Image Control in my project that loads from internet (lazy loading), i want to show a initial image in Image Control until main image load. plz help me
<DataTemplate DataType="{x:Type local:MyData}">
...
<Image Width="50" Height="50" Source="{Binding Path=profile_image_url_https, FallbackValue=profile_image_url_https}" HorizontalAlignment="Left">
...
</DataTemplate>
You might be able to make it work using TargetNullValue on the binding, only set the image property when it is loaded.
e.g.
<BitmapImage x:Key="DefaultImage" UriSource="Images/Error.ico" />
<Image Source="{Binding TestBitmapImage,
TargetNullValue={StaticResource DefaultImage}}" />
private BitmapImage _TestBitmapImage = null;
public BitmapImage TestBitmapImage
{
get { return _TestBitmapImage; }
set
{
if (_TestBitmapImage != value)
{
_TestBitmapImage = value;
PropertyChanged.Notify(() => this.TestBitmapImage);
}
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var img = new BitmapImage();
img.DownloadCompleted += (s, dcea) =>
{
TestBitmapImage = img;
};
img.BeginInit();
img.UriSource = new Uri("http://www.gravatar.com/avatar/c35af79e54306caedad37141f13de30c?s=128&d=identicon&r=PG");
img.EndInit();
}
The FlowDocumentReader has two menu items in its ContextMenu, Copy and Select All. I'd like to add an additional MenuItem to it and have tried this:
private void FlowDocumentReader_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
flowDocumentReader.ContextMenu.Items.Clear();
MenuItem menuItem = new MenuItem();
menuItem.Header = "Test";
flowDocumentReader.ContextMenu.Items.Add(menuItem);
}
additionally I've tried this:
private void FlowDocumentReader_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
MenuItem menuItem = new MenuItem();
menuItem.Header = "Test";
flowDocumentReader.ContextMenu.Items.Add(menuItem);
}
where I don't clear the items in the context menu and attempt to append it. Neither of these work.
I can create my own menu like so:
private void FlowDocumentReader_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
MenuItem menuItem = new MenuItem();
menuItem.Header = "Test";
flowDocumentReader.ContextMenu.Items.Add(menuItem);
e.Handled = true;
ContextMenu menu = new ContextMenu();
MenuItem a = new MenuItem();
a.Header = "A";
menu.Items.Add(a);
MenuItem b = new MenuItem();
b.Header = "B";
menu.Items.Add(b);
flowDocumentReader.ContextMenu.Items.Clear();
flowDocumentReader.ContextMenu = menu;
menu.IsOpen = true;
}
And that'll show up, but what I'd like to have is the Copy and Select All menu items as well as A and B.
Ideas?
You could also do this in the xaml for the FlowDocument:
<FlowDocument.ContextMenu>
<ContextMenu>
<MenuItem Header="{Resx Copy}" Command="Copy"/>
<MenuItem Header="{Resx SelectAll}" Command="SelectAll"/>
<MenuItem Header="{Resx CustomCommand}" Command="{Binding CustomCommand}"/>
</ContextMenu>
</FlowDocument.ContextMenu>
(Headers conveniently localized thanks to Grant Frisken's Resx Extension) :)
If this needs to be applied to many FlowDocuments, you could also define it in a default style somewhere:
<Style TargetType="FlowDocument">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{Resx Copy}" Command="Copy"/>
<MenuItem Header="{Resx SelectAll}" Command="SelectAll"/>
<MenuItem Header="{Resx CustomCommand}" Command="{Binding CustomCommand}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
The solution I arrived at was to simply recreate those MenuItems on the new Menu and cancel the display of the built-in ContextMenu that is normally displayed. There are a number of built-in ApplicationCommands which can be incorporated into your own custom ContextMenu and the implementation of this is quite straightforward.
Assume that I've got a ContextMenu created from some method, GetContextMenu(), the following event handler rejects the opening of the built-in ContextMenu and substitutes the one returned from the call to GetContextMenu() and adds in the Copy command (Select All is similar).
private void flowDocumentReader_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
e.Handled = true; // keeps the built-in one from opening
ContextMenu myMenu = GetContextMenu();
MenuItem copyMenuItem = new MenuItem();
copyMenuItem.Command = ApplicationCommand.Copy;
copyMenuItem.CommandTarget = myFlowDocument;
myMenu.Items.Add(copyMenuItem);
ShowMenu(myMenu);
}
private void ShowMenu(ContextMenu menu)
{
menu.Placement = PlacementMode.MousePoint;
menu.PlacementRectangle = new Rect(0.0, 0.0, 0.0, 0.0);
menu.IsOpen = true;
}