WPF ColorAnimation for a Brush property - wpf

I wonder if someone can help me - I've got a label which I need to be able to cross-fade between any 2 colors when a method is called in the code behind.
My best attempt so far:
Private OldColor as Color = Colors.White
Sub SetPulseColor(ByVal NewColor As Color)
Dim F As New Animation.ColorAnimation(OldColor, NewColor, New Duration(TimeSpan.Parse("00:00:01")))
OldColor = NewColor
F.AutoReverse = False
PulseLogo.BeginAnimation(Label.ForegroundProperty, F)
End Sub
The problem I have is that ColorAnimation returns a Media.Color and The property type for Foreground is Brush.
I know how to create the appropriate brush but not how to do it in an animation.
From Googling, it seems I need a converter:
<ValueConversion(GetType(SolidColorBrush), GetType(SolidColorBrush))> _
Public Class ColorConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
Dim Color As Color = DirectCast(value, Color)
Return New SolidColorBrush(Color)
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Return Nothing
End Function
End Class
but all the examples I've seen bind it to the animation in XAML - And I'd like to do it in the code behind...
Can someone please point me in the right direction?
Thanks

The usual solution to this is not to use a converter, but instead to animate the Color of the Brush. However, to do this you need a PropertyPath, which in turn means you need a storyboard:
Storyboard s = new Storyboard();
s.Duration = new Duration(new TimeSpan(0, 0, 1));
s.Children.Add(F);
Storyboard.SetTarget(F, PulseLogo);
Storyboard.SetTargetProperty(F, new PropertyPath("Foreground.Color"));
s.Begin();
(pardon C# syntax)
Note the property path in the SetTargetProperty call, which traverses down through the Foreground property and into the resulting brush's Color property.
You can also use this technique to animate individual gradient stops in a gradient brush, etc.

ColorAnimation colorChangeAnimation = new ColorAnimation();
colorChangeAnimation.From = VariableColour;
colorChangeAnimation.To = BaseColour;
colorChangeAnimation.Duration = timeSpan;
PropertyPath colorTargetPath = new PropertyPath("(Panel.Background).(SolidColorBrush.Color)");
Storyboard CellBackgroundChangeStory = new Storyboard();
Storyboard.SetTarget(colorChangeAnimation, BackGroundCellGrid);
Storyboard.SetTargetProperty(colorChangeAnimation, colorTargetPath);
CellBackgroundChangeStory.Children.Add(colorChangeAnimation);
CellBackgroundChangeStory.Begin();
//VariableColour & BaseColour are class of Color, timeSpan is Class of TimeSpan, BackGroundCellGrid is class of Grid;
//no need to create SolidColorBrush and binding to it in XAML;
//have fun!

Related

Showing saved Xaml Canvas through binding

I'm saving an svg image as xaml string in a database. Next I record this xaml string in a property of a class (MyClass.XamlString) .
On my form I have a Canvas, and I want MyClass.XamlString to be the child of the canvas.
This is what I have:
Dim MyBinding As New Binding("XamlString")
MyBinding.Source = MyClass
MyBinding.Converter = New clsXamlToCanvasConverter
CanvasOnForm.SetBinding(ContentPresenter.ContentProperty, MyBinding)
and
Friend Class clsXamlToCanvasConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.Convert
Dim ConvertedCanvas As Canvas = System.Windows.Markup.XamlReader.Parse("<Canvas xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">" & value & "</Canvas>")
Return ConvertedCanvas
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException
End Function
End Class
In debugging I see the clsXamlToCanvasConverter creates a canvas with children from the xaml string, this is working correctly although you might say the extra canvas is redundant.
My guess is this is where I'm going wrong: CanvasOnForm.SetBinding(ContentPresenter.ContentProperty, MyBinding)
The code runs fine, the problem is I don't see the image displayed.
I've been searching a lot on this and I've seen this post WPF What is the correct way of using SVG files as icons in WPF , but apparently this hasn't helped me out.
Any ideas?

Attached property not updating my view model

I used the technique presented in this SO post (Ray Burn's answer) to bind my RichTextBox to the underlying string-type property of my View Model (VM). The problem I'm facing is that the underlying field doesn't appear to be updating when user types something in the RichTextBox, even after focus changes. Here's the relevant XAML (SyntaxHighlighterTextBox is a custom control that inherits from RichTextBox):
<local:SyntaxHighlighterTextBox
local:RichTextBoxHelper.DocumentXaml="{Binding Query, UpdateSourceTrigger=PropertyChanged}" />
Here is the Query property of the underlying VM:
Public Property Query() As String
Get
Return mQuery
End Get
Set(ByVal value As String)
mQuery = value
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Query"))
End Set
End Property
Edit
Here's my handler's code:
Public Shared Sub MyHandler(obj As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim rtb = DirectCast(obj, SyntaxHighlighterTextBox)
Dim xaml = GetDocumentXaml(rtb)
Dim doc = New FlowDocument()
range = New TextRange(doc.ContentStart, doc.ContentEnd)
range.Load(New MemoryStream(Encoding.UTF8.GetBytes(xaml)), DataFormats.Text)
rtb.Document = doc
AddHandler range.Changed, Sub(obj2, e2)
If rtb.Document Is doc Then
Dim buffer As New MemoryStream()
range.Save(buffer, DataFormats.Text)
SetDocumentXaml(rtb, Encoding.UTF8.GetString(buffer.ToArray()))
End If
End Sub
End Sub

Load WPF UserControl dynamically

I have an abstract base class aComponent and sub-classes CriteriaList, Question, etc.
For each sub-class I have two corresponding UserControls which have names like ueCriteriaList, ubCriteriaList, ueQuestion, ubQuestion, etc. (ue stands short for "UserControl with input elements", and ub for "UserControl with buttons".)
The DataContext is the "ControllerClass" with a property with my_aComponent as the getter for the actual aComponent instance. When the aComponent instance changes (for example to an instance of CriteriaList), I want to load the corresponding UserControls (in this case ueCriteriaList and ubCriteriaList).
I have two converters ueControlConverter and ubControlConverter which take the class name (e.g. CriteriaList) and return a UserControl instance (in this case, ueCriteriaList).
Public Class ueControlConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object,
culture As System.Globalization.CultureInfo) As Object
Implements IValueConverter.Convert
Dim aComp As aComponent = value
Dim assemblyKlassenname As String = aComp.GetType.ToString
Dim assemblyName As String = Left(assemblyKlassenname,
assemblyKlassenname.IndexOf(".") + 1)
Dim klassenName As String = Right(assemblyKlassenname,
assemblyKlassenname.IndexOf(".") - 1)
Dim t As Type = Type.GetType(assemblyName & "ue" & klassenName)
Dim o As UserControl = Activator.CreateInstance(t)
o.DataContext = value
Return o
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object,
culture As System.Globalization.CultureInfo) As Object
Implements IValueConverter.ConvertBack
Return value
End Function
End Class
In XAML I have two ContentControls which bind Content="{Binding Path=my_aComponent, Converter={StaticResource _ueControlConverter} and Content="{Binding Path=my_aComponent, Converter={StaticResource _ubControlConverter}. The right UserControls are shown but without binding to my_aComponent.
What can I do?
You need to also provide the DataContext property through binding for your ContentControl objects.

Simple example of using DrawImage in VB WPF

I was hoping to figure this out on my own, but after hours of Googling I have countless examples that I can't get to work. Here's the scenario. Hopefully someone can provide a simple solution.
I have a VB WPF application. It communicates with a particular web service to get a Base64 string for an image. I am able to convert this string to a System.Drawing.Image object using the following code:
Public Function Base64ToImage(ByVal base64str As String) As System.Drawing.Image
'Setup image and get data stream together
Dim img As System.Drawing.Image
Dim MS As System.IO.MemoryStream = New System.IO.MemoryStream
Dim b64 As String = base64str.Replace(" ", "+")
Dim b() As Byte
'Converts the base64 encoded msg to image data
b = Convert.FromBase64String(b64)
MS = New System.IO.MemoryStream(b)
'creates image
img = System.Drawing.Image.FromStream(MS)
Return img
End Function
I then open a popup window and all I want to do is display this image in the popup window. The examples I have found rely on using the PaintEventArgs, but I'm not sure how that works and it doesn't seem relevant in this case. The best I've been able to do is to get the image to display on the screen, but it's not actually attached to the popup window. I did that using the following code, which is a method inside the popup window class:
Dim img as System.Drawing.Image = Base64ToImage(base64string)
Dim gr As System.Drawing.Graphics = System.Drawing.Graphics.FromHwnd(New Interop.WindowInteropHelper(Me).Handle)
gr.DrawImage(img, 10, 10, 500, 800)
gr.Dispose()
This displayed the image, but it seemed to appear at position 10,10 of the screen, not of the popup window.
System.Drawing is a WinForms-Namespace, so it doesn't work well with WPF. You can use this converter in order to convert a base64 string to a bitmapsource that can be used by WPF:
<ValueConversion(GetType(String), GetType(BitmapSource))> _
Public Class Base64ToImageConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
Try
Return Base64ToImage(value)
Catch ex As Exception
If TypeOf parameter Is BitmapSource Then
Return parameter
End If
Return Binding.DoNothing
End Try
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Try
Return ImageToBase64(value)
Catch ex As Exception
Return Binding.DoNothing
End Try
End Function
Public Shared Function Base64ToImage(ByVal imageString As String) As BitmapSource
Dim buffer() As Byte = System.Convert.FromBase64String(imageString)
Dim stream As New System.IO.MemoryStream(buffer)
Dim result As New BitmapImage()
With result
.BeginInit()
.StreamSource = stream
.EndInit()
End With
Return result
End Function
Public Shared Function ImageToBase64(ByVal image As BitmapSource) As String
Dim encoder As New PngBitmapEncoder
encoder.Frames.Add(BitmapFrame.Create(image))
Dim stream As New System.IO.MemoryStream
encoder.Save(stream)
stream.Seek(0, IO.SeekOrigin.Begin)
Dim buffer() As Byte = New System.IO.BinaryReader(stream).ReadBytes(stream.Length)
stream.Close()
Dim result As String = System.Convert.ToBase64String(buffer)
Return result
End Function
End Class
Using this converter, you can expose the base64 string as a property on an object, and bind the Source property of an image control to it.
EDIT: here is an example of how to use the converter:
<Window.Resources>
<s:String x:Key="TestImageString">iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAABjUExURXK45////6fT8PX6/bTZ8onE643F7Pf7/pDH7PP5/dns+b7e9MPh9Xq86NHo947G7Hm76NTp+PL4/bHY8ojD67rc85bK7b3e9MTh9dLo97vd8/D3/Hy96Xe76Nfr+H+/6f///1bvXooAAAAhdFJOU///////////////////////////////////////////AJ/B0CEAAACHSURBVHjaXI/ZFoMgEEMzLCqg1q37Yv//KxvAlh7zMuQeyAS8d8I2z8PT/AMDShWQfCYJHL0FmlcXSQTGi7NNLSMwR2BQaXE1IfAguPFx5UQmeqwEHSfviz7w0BIMyU86khBDZ8DLfWHOGPJahe66MKe/fIupXKst1VXxW/VgT/3utz99BBgA4P0So6hyl+QAAAAASUVORK5CYIII</s:String>
<t:Base64ToImageConverter x:Key="converter"/>
<t:ImageToBase64Converter x:Key="backConverter"/>
<BitmapImage x:Key="defaultImage" UriSource="/delete_24.png"/>
</Window.Resources>
<StackPanel>
<Image x:Name="Image" Source="{Binding Source={StaticResource TestImageString}, Converter={StaticResource converter}, ConverterParameter={StaticResource defaultImage}}" Stretch="None"/>
<TextBlock x:Name="ConvertedImage" Text="{Binding ElementName=Image, Path=Source, Converter={StaticResource backConverter}, ConverterParameter={StaticResource defaultImage}}"/>
<Image x:Name="CheckImage" Source="{Binding ElementName=ConvertedImage, Path=Text, Converter={StaticResource converter}, ConverterParameter={StaticResource defaultImage}}" Stretch="None"/>
</StackPanel>
Instead of a static string resource, you could use any property on a bound object which returns an image in a format which is recognized by WPF and has been base64 encoded.

Periodically update silverlight view with MVVM

I am trying to use MVVM in Silverlight, but I am quite new to it so I am not quite sure on some things. I have a silverlight page which displays the progress of some server side operation. The current progress comes from a web service and should be refreshed every few seconds (lets say 10 seconds for the sake of the argument).
What is the best way to implement this? The options I could think of was:
Initalize a DispatcherTimer in the Initalize method of my ViewModel and refresh the view from the DispatcherTimer event (putting the timer details in the ViewModel)
Create a wrapper arround DispatcherTimer (e.g. PeriodicCommandExecutor) which would be a Control or resource similar to the Timer control in WindowsForms with a command property that I bind to a Refresh command in the ViewModel (putting the timer details in the View)
I think the second option is preferred, because it makes the ViewModel easier to test and DispatcherTimer is an UI implementation detail which I don't want in my ViewModel propably. Do you agree?
If yes, how would you create such a wrapper. I started doing an DependencyObject with attached properties, but I am not sure how to forward the property values like Interval to the internal DispatcherTimer. Silverlight doesn't seem to provide any events when the dependency properties change and DispatcherTimer is not a DependencyObject so I can't databind directly to its properties.
Thanks!
Why use a DispatcherTimer? Why not use an ordinary System.Threading.Timer, which will fire its callback on a background thread?
If you put your UI progress update somewhere inconspicious (i.e. not in the centre of the UI, maybe in a bottom corner or status bar), then have the background timer chugging away while the user carries on with what they were doing. The progress value can be populated into the viewmodel, and shown on the UI using binding. This way you don't have to tie up the UI thread making web service calls.
At the end I solved my dillema creating a behavior which periodically executes a refresh command on the ViewModel which you can specify.
The code for the behavior is like this
(sorry for VB code):
Option Strict On
Imports System.Windows.Threading
Imports System.Windows.Interactivity
Namespace View.Behaviors
Public Class RefreshBehavior
Inherits Behavior(Of FrameworkElement)
Public Property Command As ICommand
Get
Return DirectCast(GetValue(CommandProperty), ICommand)
End Get
Set(ByVal value As ICommand)
SetValue(CommandProperty, value)
End Set
End Property
Public Shared ReadOnly CommandProperty As DependencyProperty = _
DependencyProperty.Register("Command", _
GetType(ICommand), GetType(RefreshBehavior), _
New PropertyMetadata(Nothing))
Public Property CommandParameter As Object
Get
Return GetValue(CommandParameterProperty)
End Get
Set(ByVal value As Object)
SetValue(CommandParameterProperty, value)
End Set
End Property
Public Shared ReadOnly CommandParameterProperty As DependencyProperty = _
DependencyProperty.Register("CommandParameter", _
GetType(Object), GetType(RefreshBehavior), _
New PropertyMetadata(Nothing))
Public Property Interval As TimeSpan
Get
Return DirectCast(GetValue(IntervalProperty), TimeSpan)
End Get
Set(ByVal value As TimeSpan)
SetValue(IntervalProperty, value)
End Set
End Property
Public Shared ReadOnly IntervalProperty As DependencyProperty = _
DependencyProperty.Register("Interval", _
GetType(TimeSpan), GetType(RefreshBehavior), _
New PropertyMetadata(TimeSpan.Zero, AddressOf OnIntervalUpdate))
Public Property Enabled As Boolean
Get
Return DirectCast(GetValue(EnabledProperty), Boolean)
End Get
Set(ByVal value As Boolean)
SetValue(EnabledProperty, value)
End Set
End Property
Public Shared ReadOnly EnabledProperty As DependencyProperty = _
DependencyProperty.Register("Enabled", _
GetType(Boolean), GetType(RefreshBehavior), _
New PropertyMetadata(False, AddressOf OnEnabledUpdate))
Dim WithEvents timer As New DispatcherTimer()
Private Shared Sub OnEnabledUpdate(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim enable As Boolean = CType(e.NewValue, Boolean)
Dim executor As RefreshBehavior = CType(d, RefreshBehavior)
If Not executor.attached Then Return
Dim timer As DispatcherTimer = executor.timer
If enable AndAlso Not timer.IsEnabled Then
timer.Start()
ElseIf Not enable AndAlso Not timer.IsEnabled Then
timer.Stop()
End If
End Sub
Private Shared Sub OnIntervalUpdate(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim executor As RefreshBehavior = CType(d, RefreshBehavior)
Dim timer As DispatcherTimer = executor.timer
timer.Interval = CType(e.NewValue, TimeSpan)
End Sub
Private WithEvents attachedObject As FrameworkElement
Private Sub OnUnload(ByVal sender As Object, ByVal e As EventArgs) Handles attachedObject.Unloaded
timer.Stop()
End Sub
Private attached As Boolean = False
Protected Overrides Sub OnAttached()
attached = True
attachedObject = AssociatedObject
If Enabled Then timer.Start()
MyBase.OnAttached()
End Sub
Protected Overrides Sub OnDetaching()
timer.Stop()
attached = False
attachedObject = Nothing
MyBase.OnDetaching()
End Sub
Private Sub OnTick(ByVal sender As Object, ByVal e As EventArgs) Handles Timer.Tick
Dim cmd = Command
Dim parameter = CommandParameter
If Interval < TimeSpan.MaxValue AndAlso cmd IsNot Nothing AndAlso cmd.CanExecute(parameter) Then
cmd.Execute(parameter)
End If
End Sub
End Class
End Namespace
You can use it like this:
<i:Interaction.Behaviors>
<Behaviors:RefreshBehavior Enabled="True" Interval="0:0:10" Command="{Binding RefreshPageCommand}" />
</i:Interaction.Behaviors>
I hope it helps someone with a similar problem.

Resources