Clone element to display as an image/bitmap - wpf

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);
}

Related

How to prevent a memory leak when displaying an image by binding to a bytearray?

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;
}

How to fade an image in on load silverlight?

Can anyone point me to a resource on how to "fade in" an image on load in silverlight? Basically I have a listbox of items that are returned from a web service and some times the images take a little longer to load, so I wanted to fade them in as they download. I read that I might need a storyboard for this effect. Is this the best route or are there alternatives?
I'm not aware of any alternatives. The StoryBoard is the best route. You could just animate the Opacity of the image from 0 to 100.
Put the Storyboard in the resources for the UserControl () or in the App.xaml.
Then in your OnOpened event (as you mentioned in the comment):
protected void OnOpened(object sender, EventArgs e)
{
// params might be incorrect
this.fadeInStoryBoard.Stop();
// your image controls will need x:names set
this.fadeInAnimation.SetValue(Storyboard.TargetNameProperty, ((Image)sender).Name);
this.fadeInStoryBoard.Start();
}
The example storyboard is from:
http://www.codeproject.com/KB/silverlight/AgDynAnimations.aspx
and starting the storyboard: http://blogs.msdn.com/b/silverlight_sdk/archive/2008/03/26/target-multiple-objects-properties-with-one-animation-silverlight.aspx
Here's a utility function to perform the task
private void FadeIn(UIElement uilelement)
{
uilelement.Opacity = 0.0;
uilelement.Visibility = Visibility = Visibility.Visible;
var timeline = new DoubleAnimation() { To = 1.0, Duration =TimeSpan.FromSeconds(3.0) };
Storyboard.SetTarget(timeline, uilelement);
Storyboard.SetTargetProperty(timeline, new PropertyPath(UIElement.OpacityProperty));
var sb = new Storyboard();
sb.Children.Add(timeline);
sb.Begin();
}
function void image1_Opened(object sender, EventArgs e)
{
FadeIn(sender as Image);
}
You should set the Image to Collapsed, or set its initial Opacity to 0.
<Image Source="{Binding ImagePath}"
Name="image1"
Visibility="Collapsed"
ImageOpened="image1_ImageOpened" />

Expanders in Grid

This is going to be straight forward no doubt, but for what ever reason, my mind is drawing a blank on it.
I've got a small, non-resizeable window (325x450) which has 3 Expanders in it, stacked vertically. Each Expander contains an ItemsControl that can potentially have a lot of items in and therefore need to scroll.
What I can't seem to get right is how to layout the Expanders so that they expand to fill any space that is available without pushing other elements off the screen. I can sort of achieve what I'm after by using a Grid and putting each expander in a row with a * height, but this means they are always taking up 1/3 of the window each which defeats the point of the Expander :)
Crappy diagram of what I'm trying to achieve:
This requirement is a little unusal because the you want the state of the Children in the Grid to decide the Height of the RowDefinition they are in.
I really like the layout idea though and I can't believe I never had a similar requirement myself.. :)
For a reusable solution I would use an Attached Behavior for the Grid.
The behavior will subscribe to the Attached Events Expander.Expanded and Expander.Collapsed and in the event handlers, get the right RowDefinition from Grid.GetRow and update the Height accordingly. It works like this
<Grid ex:GridExpanderSizeBehavior.SizeRowsToExpanderState="True">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Expander Grid.Row="0" ... />
<Expander Grid.Row="1" ... />
<Expander Grid.Row="2" ... />
<!-- ... -->
</Grid>
And here is GridExpanderSizeBehavior
public class GridExpanderSizeBehavior
{
public static DependencyProperty SizeRowsToExpanderStateProperty =
DependencyProperty.RegisterAttached("SizeRowsToExpanderState",
typeof(bool),
typeof(GridExpanderSizeBehavior),
new FrameworkPropertyMetadata(false, SizeRowsToExpanderStateChanged));
public static void SetSizeRowsToExpanderState(Grid grid, bool value)
{
grid.SetValue(SizeRowsToExpanderStateProperty, value);
}
private static void SizeRowsToExpanderStateChanged(object target, DependencyPropertyChangedEventArgs e)
{
Grid grid = target as Grid;
if (grid != null)
{
if ((bool)e.NewValue == true)
{
grid.AddHandler(Expander.ExpandedEvent, new RoutedEventHandler(Expander_Expanded));
grid.AddHandler(Expander.CollapsedEvent, new RoutedEventHandler(Expander_Collapsed));
}
else if ((bool)e.OldValue == true)
{
grid.RemoveHandler(Expander.ExpandedEvent, new RoutedEventHandler(Expander_Expanded));
grid.RemoveHandler(Expander.CollapsedEvent, new RoutedEventHandler(Expander_Collapsed));
}
}
}
private static void Expander_Expanded(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
Expander expander = e.OriginalSource as Expander;
int row = Grid.GetRow(expander);
if (row <= grid.RowDefinitions.Count)
{
grid.RowDefinitions[row].Height = new GridLength(1.0, GridUnitType.Star);
}
}
private static void Expander_Collapsed(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
Expander expander = e.OriginalSource as Expander;
int row = Grid.GetRow(expander);
if (row <= grid.RowDefinitions.Count)
{
grid.RowDefinitions[row].Height = new GridLength(1.0, GridUnitType.Auto);
}
}
}
If you don't mind a little code-behind, you could probably hook into the Expanded/Collapsed events, find the parent Grid, get the RowDefinition for the expander, and set the value equal to * if its expanded, or Auto if not.
For example,
Expander ex = sender as Expander;
Grid parent = FindAncestor<Grid>(ex);
int rowIndex = Grid.GetRow(ex);
if (parent.RowDefinitions.Count > rowIndex && rowIndex >= 0)
parent.RowDefinitions[rowIndex].Height =
(ex.IsExpanded ? new GridLength(1, GridUnitType.Star) : GridLength.Auto);
And the FindAncestor method is defined as this:
public static T FindAncestor<T>(DependencyObject current)
where T : DependencyObject
{
// Need this call to avoid returning current object if it is the
// same type as parent we are looking for
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
};
return null;
}

Set the caret/cursor position to the end of the string value WPF textbox

I am try to set the caret/cursor position to the end of the string value in my WPF textbox when I open my window for the first time. I use the FocusManager to set the focus on my textbox when my window opens.
Nothing seems to work. Any ideas?
Note, I am using the MVVM pattern, and I included only a portion of the XAML from my code.
<Window
FocusManager.FocusedElement="{Binding ElementName=NumberOfDigits}"
Height="400" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Grid.Column="0" Grid.Row="0"
x:Name="NumberOfDigits"
IsReadOnly="{Binding Path=IsRunning, Mode=TwoWay}"
VerticalContentAlignment="Center"
Text="{Binding Path=Digits, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="0" Grid.Row="1"
Margin="10,0,10,0"
IsDefault="True"
Content="Start"
Command="{Binding StartCommand}"/>
</Grid>
</Window>
You can set the caret position using CaretIndex property of a TextBox. Please bear in mind that this is not a DependencyProperty. Nevertheless, you may still set it in XAML like this:
<TextBox Text="123" CaretIndex="{x:Static System:Int32.MaxValue}" />
Please remember to set CaretIndex after Text property or else it will not work. Thus it probably won't work if you bind to Text like in your example. In that case, simply use code-behind like this.
NumberOfDigits.CaretIndex = NumberOfDigits.Text.Length;
You may also create a Behavior, which, while still being code-behind, has the advantage of being reusable.
Example of a simple behavior class, using the focus event of the textbox:
class PutCursorAtEndTextBoxBehavior: Behavior<UIElement>
{
private TextBox _textBox;
protected override void OnAttached()
{
base.OnAttached();
_textBox = AssociatedObject as TextBox;
if (_textBox == null)
{
return;
}
_textBox.GotFocus += TextBoxGotFocus;
}
protected override void OnDetaching()
{
if (_textBox == null)
{
return;
}
_textBox.GotFocus -= TextBoxGotFocus;
base.OnDetaching();
}
private void TextBoxGotFocus(object sender, RoutedEventArgs routedEventArgs)
{
_textBox.CaretIndex = _textBox.Text.Length;
}
}
Then, in your XAML, you attach the behavior like so:
<TextBox x:Name="MyTextBox" Text="{Binding Value}">
<i:Interaction.Behaviors>
<behaviors:PutCursorAtEndTextBoxBehavior/>
</i:Interaction.Behaviors>
</TextBox>
This worked for me. I am also using the MVVM pattern. However, my purpose for using a MMVM is to make unit testing possible and to make it easier to update my UI (loosely coupled). I don't see myself unit testing the position of the cursor so I don't mind resorting to the code behind for this simple task.
public ExpeditingLogView()
{
InitializeComponent();
this.Loaded += (sender, args) =>
{
Description.CaretIndex = Description.Text.Length;
Description.ScrollToEnd();
Description.Focus();
};
}
#Louis solution will not work if textbox used in template binding or any type of lazy bindings or lazy value assignments
So if the textbox used for example in Datagrid cell as a template that solution will need for tiny modification to work
and that is subscribing to text changed event
class PutCursorAtEndTextBoxBehavior : Behavior<UIElement>
{
private TextBox _textBox;
protected override void OnAttached()
{
base.OnAttached();
_textBox = AssociatedObject as TextBox;
if (_textBox == null)
{
return;
}
_textBox.GotFocus += TextBoxGotFocus;
// to make it work with binding
_textBox.TextChanged += TextBoxGotFocus;
}
protected override void OnDetaching()
{
if (_textBox == null)
{
return;
}
_textBox.GotFocus -= TextBoxGotFocus;
_textBox.TextChanged -= TextBoxGotFocus;
base.OnDetaching();
}
private void TextBoxGotFocus(object sender, RoutedEventArgs routedEventArgs)
{
_textBox.CaretIndex = _textBox.Text.Length;
}
}
None of the answers here worked for me. I'm using binding for the TextBox and needed to move the caret right after the window poped up. This did it for me:
public MyWindow()
{
InitializeComponent();
ContentRendered += (sender, args) =>
{
MyTextBox.CaretIndex = MyTextBox.Text.Length;
MyTextBox.ScrollToEnd(); // not necessary for single line texts
MyTextBox.Focus();
};
}
Similar to Ceranski answer. Instead of adding to the Loaded event we add to ContentRendered.
In case of multiline TextBox setting cursor is not enough.
Try this:
NumberOfDigits.ScrollToEnd();
In WPF if line is long enough it is important also to scroll to the end of the line. So i'm using the following lines:
text_Box.Text = text;
text_Box.CaretIndex = text.Length;
text_Box.ScrollToHorizontalOffset(double.MaxValue);
// or you can use this - for me works also
// text_Box.ScrollToHorizontalOffset(text_Box.GetRectFromCharacterIndex(openFileDialog.FileName.Length).Right);
but read this caution (for me it's fine - probably already fixed):
TextBox ScrollToHorizontalOffset will not scroll after text is long enough
Try this given method: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/position-the-cursor-at-the-beginning-or-end-of-text?view=netframeworkdesktop-4.8
textBox.Select(2,0);
For some reasons I had to use :
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
textBox.CaretIndex = textBox.Text.Length;
textBox.ScrollToEnd();
}));
I wanted to create a UserControl / View with a pre-populated textbox bound to a ViewModel, and when the control opens up, focus is automatically set on the textbox and the caret position at the end. This was the only way I got it to work:
public TextBoxDialogView()
{
InitializeComponent();
TextBox.GotKeyboardFocus += (sender, args) =>
{
TextBox.CaretIndex = TextBox.Text.Length;
};
_ = TextBox.Focus();
}
Seems to work nicely so far...

Find UI element corresponding to an item in a Silverlight ItemsControl

I have a list of strings displayed by a Silverlight ItemsControl. The DataTemplate is a Border control with a TextBlock as its child. How can I access the border control corresponding to an item? For example, I might want to do this to change the background color.
An easier way to do this is to grab the Parent of the textblock and cast it as a Border. Here is a quick example of this:
Xaml
<Grid>
<ItemsControl x:Name="items">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border>
<TextBlock MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave" Text="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Code behind
public Page()
{
InitializeComponent();
items.ItemsSource = new string[] { "This", "Is", "A", "Test" };
}
private void TextBlock_MouseEnter(object sender, MouseEventArgs e)
{
var tx = sender as TextBlock;
var bd = tx.Parent as Border;
bd.Background = new SolidColorBrush(Colors.Yellow);
}
private void TextBlock_MouseLeave(object sender, MouseEventArgs e)
{
var tx = sender as TextBlock;
var bd = tx.Parent as Border;
bd.Background = new SolidColorBrush(Colors.White);
}
The example sets the background on the border by grabbing the parent of the textbox.
You can override the ItemsControl.GetContainerForItemOverride method and save the object-container pairs in a dictionary.
see this: http://msdn.microsoft.com/en-us/library/bb613579.aspx and this: http://blogs.msdn.com/wpfsdk/archive/2007/04/16/how-do-i-programmatically-interact-with-template-generated-elements-part-ii.aspx. Unfortunately, it won't work in SL because SL DataTemplate class doesn't have the FindName method.

Resources