Simple example of using DrawImage in VB WPF - 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.

Related

WPF in VB Text Box 'System.NullReferenceException'

so here is simple WPF application I made to practice OO concepts. It has the user input a number in feet and converts to meters by outputting a message box.
Class MainWindow
Dim lengthInMeters = txtBox1.Text '***An exception of type "System.NullReferenceException" occurred***
Private Sub button_Click(sender As Object, e As RoutedEventArgs) Handles button.Click
MessageBox.Show(COptions.GetLength(lengthInMeters), "Conversion Successful!", MessageBoxButton.OK)
End Sub
End Class
And my other Class file:
Public Class COptions
Public Shared Function GetLength(lengthInMeters) As Double
Return lengthInMeters / 3.28
End Function
End Class
Also, here is the XAML for the textbox:
<TextBox
x:Name="txtBox1"
x:FieldModifier="public"
HorizontalAlignment="Left"
Height="23"
Margin="200,140,0,0"
TextWrapping="Wrap"
VerticalAlignment="Top"
Width="120"
enderTransformOrigin="1.29,-3.252"
Grid.Column="1"
/>
I am getting a NullReference at "Dim lengthInMeters = txtBox1.Text" and I believe I need to instantiate my object reference? But how?
Thank you for your help!
Fields are initialized before the constructor runs, and GUI components initialized in the constructor, so txtBox1 has not been initialized at the point you are using it, as you have found out. Even if it did work, it would capture the value of the text box at that point, which is before the user has had a chance to enter anything.
Instead, move the Dim lengthInMeters = txtBox1.Text line into your button click handler, so that you get the current value of the text box just before you use it. Your current code is also relying on some implicit type conversions - I would recommend turning Option Strict On to catch these, and use explicit conversions.
Private Sub button_Click(sender As Object, e As RoutedEventArgs) Handles button.Click
Dim lengthInMeters = CDbl(txtBox1.Text)
MessageBox.Show(COptions.GetLength(lengthInMeters), "Conversion Successful!", MessageBoxButton.OK)
End Sub
Public Class COptions
Public Shared Function GetLength(lengthInMeters As Double) As Double
Return lengthInMeters / 3.28
End Function
End Class

How To Link ComboBoxItem to File

This question is a follow-up to one I asked and got answered here: How to display XPS document using a selected combobox item
I've created a WPF app using VB 2010. I set the comboboxitems via the XAML. However, I can't seem to figure out how to set the value of each item to a file path.
The objective is for a user to be able to select an item from a drop-down list, then that selection opens an XPS file in the DocumentViewer. The code below was provided to me by COMPETENT_TECH (thanks) to read and display the value of the selected comboboxitem in the DocumentViewer.
The path to the files I want opened is C:\folder\file.xps
Private Sub Button4_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button4.Click
Try
Dim sFileName As String
sFileName = DirectCast(ComboBox1.SelectedValue, String)
If Not String.IsNullOrEmpty(sFileName) Then
Dim theDocument As New System.Windows.Xps.Packaging.XpsDocument(sFileName, System.IO.FileAccess.Read)
DocumentViewer1.Document = theDocument.GetFixedDocumentSequence()
End If
Catch ex As Exception
MessageBox.Show("ERROR: " & ex.Message)
End Try
End Sub
Thanks in advance for your assistance.
Update
Here's the XAML I'm using:
<ComboBox Width="Auto" IsReadOnly="True" IsEditable="True" Name="ComboBox1" Height="Auto" Margin="0" Padding="1" Grid.Column="2">
<ComboBoxItem>123456</ComboBoxItem>
<ComboBoxItem>123457</ComboBoxItem>
<ComboBoxItem>123458</ComboBoxItem>
</ComboBox>
Depending on precisely how the xaml to load the combobox is specified, you will probably want to change this line:
Dim theDocument As New System.Windows.Xps.Packaging.XpsDocument(sFileName, System.IO.FileAccess.Read)
to:
Dim theDocument As New System.Windows.Xps.Packaging.XpsDocument(System.IO.Path.Combine("C:\folder", sFileName & ".xps"), System.IO.FileAccess.Read)
All that we do in the new code is combine the directory where the files are stored with the file name retrieved from the combobox.
Update
The correct way to retrieve the value from the combobox is:
If ComboBox1.SelectedValue IsNot Nothing Then
sFileName = DirectCast(ComboBox1.SelectedValue, ComboBoxItem).Content.ToString()
End If
I highly recommend you use Data Binding with that ComboBox.
create a class something like following:
Class XPSDocumentInfo
{
Public Property Title As String
Public Property FileName As String
}
Create an ObservableCollection(Of XPSDocumentInfo) and bind it to ComboBox's ItemsSource.
Use DisplayMemberPath="Title" attribute on ComboBox so that it will use Title property to show text in dropdown but since you bound collection of type XPSDocumentInfo, SelectedItem property of that ComboBox with return an object of type XPSDocumentInfo.
For example,
sFileName = DirectCast(ComboBox1.SelectedValue, String)
will be changed to
sFileName = DirectCast(ComboBox1.SelectedItem, XPSDocumentInfo).FileName

Directly binding a BitmapImage created inside a class bound to a ListBox in WPF, possible?

I am adding object directly to a ListBox, and inside this class, I've got a BitmapImage object.
I'm using an ItemTemplate :
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=ElementIcon}"></Image>
<TextBlock Text="{Binding Path=ElementName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
And I directly add object of this class :
Public Class ExplorerClass
Implements INotifyPropertyChanged
Public Property ElementType As String = Nothing
Public Property ElementName As String = Nothing
Public Property ElementContainer As String = Nothing
Public Property ElementIcon As New BitmapImage
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Public Sub New(ByVal WantedElementContainer As String, ByVal WantedElementName As String, ByVal WantedElementType As String)
ElementType = WantedElementType
ElementName = WantedElementName
ElementContainer = WantedElementContainer
Dim str As New MemoryStream
Dim IWorking As Icon = showIcon(ElementName.Substring(ElementName.LastIndexOf(".")))
IWorking.ToBitmap.Save(str, System.Drawing.Imaging.ImageFormat.Png)
ElementIcon.BeginInit()
ElementIcon.StreamSource = str
ElementIcon.EndInit()
NotifyPropertyChanged("ElementIcon")
End Sub
End Class
But, there is no pictures showed;
So, my question is : "How can I bind the BitmapImage" ?
It looks like you are not implementing the INotifyPropertyChanged Interface. Take a look at this page on MSDN. I'm not sure if this is also a problem but try setting the height and width of the image.

WPF ColorAnimation for a Brush property

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!

How to get ElementHost control, given one of WPF control's content

I am trying to get a reference to ElementHost control. For example in the below code I need to initially use the “testImage” content of the WPF user control to lunch the event. The WPF control is added at run-time, so does the ElementHost control, so I can’t use the WPF control’s name or ElementHost’s name.
My logic is to get the parent WPF user control of the “testImage”, and then get the parent ElementHost of the WPF’s user control.
But I am having troubles writing it in code. Please advise. Thanks.
<UserControl x:Class="WpfTest”
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300">
<Grid>
<Label FontSize="10" Height="24" Margin="74,16,0,0" Name="testLabel" VerticalAlignment="Top" />
<Image Name="testImage" Stretch="Uniform" HorizontalAlignment="Left" Width="64" Height="81" VerticalAlignment="Top" Margin="8,0,0,0"/>
</Grid>
</UserControl>
Here is some code that might help you. The key points are:
Name the ElementHost when you create it at runtime
Make use the the help function FindVisualChildByName() to search the WPF tree to get the desired control
I Hope this helps!
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim ElementHost1 As New System.Windows.Forms.Integration.ElementHost
Dim WpfTest1 As New WindowsApplication1.WPFTest
ElementHost1.Dock = DockStyle.Fill
ElementHost1.Name = "ElementHost1"
ElementHost1.Child = WpfTest1
Me.Controls.Add(ElementHost1)
End Sub
Private Sub GetImageReference_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim ElementHost1 As System.Windows.Forms.Integration.ElementHost = Me.Controls("ElementHost1")
Dim TheGrid As System.Windows.Controls.Grid = CType(ElementHost1.Child, WPFTest).MyGrid
Dim ImageTest As System.Windows.Controls.Image = FindVisualChildByName(TheGrid, "testImage")
Stop
End Sub
Public Function FindVisualChildByName(ByVal parent As System.Windows.DependencyObject, ByVal Name As String) As System.Windows.DependencyObject
For i As Integer = 0 To System.Windows.Media.VisualTreeHelper.GetChildrenCount(parent) - 1
Dim child = System.Windows.Media.VisualTreeHelper.GetChild(parent, i)
Dim controlName As String = child.GetValue(System.Windows.Controls.Control.NameProperty)
If controlName = Name Then
Return child
Else
Dim res = FindVisualChildByName(child, Name)
If Not res Is Nothing Then
Return res
End If
End If
Next
Return Nothing
End Function

Resources