WPF Mediaelement LoadedBehavior does't work correctly? - wpf

I am creating a simple app in WPF with VB.NET code behind. This code works perfectly. After the choosing of file, the function returns the correct numbers according to media type, the note image appears when the media is audio file and disappears when not. The media plays automatically. However I don't need to play the media automatically and when I set LoadedBehavior to Manual (instead of the default Play), strange things happen. The MediaOpened event is fired from time to time, the function returns sometimes zero, sometimes correct number, sometimes incorrect number. I am googling almost three days and I've found nothing. I'd like to make MediaElement.Play and MediaElement.Pause to show the first frame of video/image, but it's not possible without LoadedBehavior set to manual. I'd like to avoid LoadedBehavior somehow. Could someone give me a hint, please? Thank you very much in advance.
Private Sub btnFile_Click(sender As Object, e As RoutedEventArgs) Handles btnFile.Click
Try
Me.MediaElementSetting.LoadedBehavior = MediaState.Manual
Me.MediaElementSetting.UnloadedBehavior = MediaState.Close
Me.MediaElementSetting.Stop()
Me.MediaElementSetting.Close()
Me.PvwTimer.Stop()
Catch ex As Exception
'The exception occurs when LoadedBehavior is not set to manual, that's why it's unused
'MsgBox(ex.ToString)
End Try
Dim ArrayIndes as Integer = 1
OpenFileDialog(ArrayIndex)
End Sub
Sub OpenFileDialog(ByVal ArrayIndex As Integer)
Dim Dlg1 As New System.Windows.Forms.OpenFileDialog
Dlg1.ShowDialog()
If Dlg1.FileName <> "" Then
strFile = Dlg1.FileName
Me.MediaElementSetting.Source = New Uri(strFile, UriKind.RelativeOrAbsolute)
End If
End Sub
Private Sub MediaElementSetting_MediaOpened(sender As Object, e As RoutedEventArgs) Handles MediaElementSetting.MediaOpened
Dim t As Integer = AV()
End Sub
Function AV() As Integer
'Check, if the media has timespan (if it is image, audio, video w sound, video w/o sound - image has video without timespan)
If Me.MediaElementSetting.NaturalDuration.HasTimeSpan = True Then
If Me.MediaElementSetting.HasAudio = True Then
If Me.MediaElementSetting.HasVideo = True Then
'Media is a video with sound
MediaInfo = 4
Else
'Media is an audio
MediaInfo = 2
End If
Else
If Me.MediaElementSetting.HasVideo = True Then
'Media is a video without sound
MediaInfo = 3
End If
End If
Else
If Me.MediaElementSetting.HasVideo = True Then
'Media is an image
MediaInfo = 1
End If
End If
If MediaInfo = 2 Then
'Note is an image in a window. When the media is an audio it covers the mediaelement in a window
Note.Visibility = Visibility.Visible
Else
Note.Visibility = Visibility.Hidden
End If
Return MediaInfo
End Function

I'm not a VB.NET person, but here is a working example in C#.
Here's the XAML. I have a MediaElement, a Border as a stand-in for the notes image, and a button.
MainWindow.xaml
<Window x:Class="SO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SO"
Title="MainWindow"
Width="800"
Height="450">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<MediaElement x:Name="MediaElementSetting"
Margin="16" />
<Border x:Name="AudioElement"
Margin="16"
Background="Red"
Visibility="Collapsed">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White"
Text="AUDIO" />
</Border>
<Button x:Name="FileButton"
Grid.Row="1"
Margin="16"
Padding="8,4"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Click="FileButton_OnClick"
Content="File" />
</Grid>
</Window>
Here's the code-behind.
I'm initializing the MediaElement once in the constructor of the window.
In order to show the first frame of the video, I need to set ScrubbingEnabled = true.
In OpenFileDialog(), after setting the Source I call SetupMedia which immediately calls Play, Pause, and then sets the Position. This ensures that the MediaOpened event gets called.
In the OnMediaOpened handler, I call GetMediaInfo which is like your AV function and instead of returning an integer, I return an enum instead.
MainWindow.xaml.cs
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
namespace SO
{
public partial class MainWindow : Window
{
private bool isMediaOpened;
public MainWindow()
{
InitializeComponent();
MediaElementSetting.LoadedBehavior = MediaState.Manual;
MediaElementSetting.ScrubbingEnabled = true;
MediaElementSetting.UnloadedBehavior = MediaState.Close;
MediaElementSetting.MediaOpened += OnMediaOpened;
}
private void OnMediaOpened(object sender, RoutedEventArgs e)
{
isMediaOpened = true;
var mediaInfo = GetMediaInfo();
Debug.WriteLine($"{mediaInfo}");
}
private void SetupMedia()
{
MediaElementSetting.Play();
MediaElementSetting.Pause();
MediaElementSetting.Position = TimeSpan.FromSeconds(0);
}
private MediaInfo GetMediaInfo()
{
var mediaInfo = MediaInfo.None;
if (MediaElementSetting.NaturalDuration.HasTimeSpan)
{
if (MediaElementSetting.HasAudio)
{
mediaInfo = MediaElementSetting.HasVideo ? MediaInfo.VideoWithSound : MediaInfo.Audio;
}
else
{
if (MediaElementSetting.HasVideo)
{
mediaInfo = MediaInfo.VideoNoSound;
}
}
}
else
{
if (MediaElementSetting.HasVideo)
{
mediaInfo = MediaInfo.Image;
}
}
AudioElement.Visibility = mediaInfo == MediaInfo.Audio ? Visibility.Visible : Visibility.Collapsed;
return mediaInfo;
}
private void FileButton_OnClick(object sender, RoutedEventArgs e)
{
OpenFileDialog();
}
private void OpenFileDialog()
{
var dialog = new OpenFileDialog();
dialog.ShowDialog();
if (!string.IsNullOrEmpty(dialog.FileName))
{
if (isMediaOpened)
{
MediaElementSetting.Source = null;
isMediaOpened = false;
}
MediaElementSetting.Source = new Uri(dialog.FileName, UriKind.RelativeOrAbsolute);
SetupMedia();
}
}
public enum MediaInfo
{
None = 0,
Image = 1,
Audio = 2,
VideoNoSound = 3,
VideoWithSound = 4
}
}
}

Well, I accidentally found the solution, that works perfectly for me. To don't use MediaElement.LoadedBehavior = Manual at all
Private Sub MediaElementSetting_MediaOpened(sender As Object, e As RoutedEventArgs) Handles MediaElementSetting.MediaOpened
Dim t As Integer = AV()
Me.MediaElementSetting.UnloadedBehavior = MediaState.Close
Me.MediaElementSetting.LoadedBehavior = MediaState.Play
Me.MediaElementSetting.LoadedBehavior = MediaState.Pause
End Sub
And to control media with MediaElement.LoadedBehavior as well. This works for me perfectly, even when I think it's not correct. Logically, LoadedBehavior means loaded behavior.
Sub PlayMedia()
MediaElementSetting.LoadedBehavior = MediaState.Play
End Sub
Sub PauseMedia()
MediaElementSetting.LoadedBehavior = MediaState.Pause
End Sub
Sub StopMedia()
MediaElementSetting.LoadedBehavior = MediaState.Stop
End Sub

Related

XAML - How can I use a KeyBinding command to go to a new line in the textbox?

I am simply trying to move to a new line when Return + Shift are pressed.
I got this much from a previous post on here:
<TextBox.InputBindings>
<KeyBinding Key="Return" Modifiers="Shift" Command=" " />
</TextBox.InputBindings>
But I cannot find anywhere that explains how to accomplish the move to a new line within the textbox.
I cannot use the: AcceptsReturn="True" as I want return to trigger a button.
If you don't have an ICommand defined, you can attach a handler for the UIElement.PreviewKeyUp event to the TextBox. Otherwise you will have to define an ICommand implementation and assign it to the KeyBinding.Command so that the KeyBinding can actually execute. Either solution finally executes the same logic to add the linebreak.
You then use the TextBox.AppendText method to append a linebreak and the TextBox.CaretIndex property to move the caret to the end of the new line.
MainWindow.xaml
<Window>
<TextBox PreviewKeyUp="TextBox_PreviewKeyUp" />
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
private void TextBox_PreviewKeyUp(object sender, KeyEventArgs e)
{
if (!e.Key.Equals(Key.Enter)
|| !e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Shift))
{
return;
}
var textBox = sender as TextBox;
textBox.AppendText(Environment.NewLine);
textBox.CaretIndex = textBox.Text.Length;
}
}
I found a nice way to do it without using an ICommand.
Simply added this PreviewKeyDown event onto the control in xaml:
PreviewKeyDown="MessageText_PreviewKeyDown"
And this is the C# behind:
private void MessageText_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
// Get the textbox
var textbox = sender as TextBox;
// Check if we have pressed enter
if (e.Key == Key.Enter && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
{
// Add a new line where cursor is
var index = textbox.CaretIndex;
// Insert a new line
textbox.Text = textbox.Text.Insert(index, Environment.NewLine);
// Shift the caret forward to the newline
textbox.CaretIndex = index + Environment.NewLine.Length;
// Mark this key as handled by us
e.Handled = true;
}
}

How to pass value from one window to another

How do I pass a value from one a variable to a textbox after it's set? On winforms, I used to use form1.textbox1.text = variable in winforms.
I set, and get the variable from this...
Public Shared Property containerstring() As String
Get
Return m_containerstring
End Get
Set(value As String)
m_containerstring = value
End Set
End Property
Private Shared m_containerstring As String
Basically, I have a window... where the user chooses a variable, this variable is then set # containerstring. When this form closes, I wanted to push this variable to the currently open window's textbox.
I'm new to WPF, forgive the noobness.
This is how I do it for a window, and this works perfectly... for windows. I'm looking to do the same thing with a control.
Dim strWindowToLookFor As String = GetType(MainWindow).Name
Dim win = ( _
From w In Application.Current.Windows _
Where DirectCast(w, Window).GetType.Name = strWindowToLookFor _
Select w _
).FirstOrDefault
If win IsNot Nothing Then
DirectCast(win, MainWindow).Title = SelectedContainer
End If
You can make a Window closing Event like :
this.Closed += MyWindow_Closed;
and then set your variable in the corresponding method.
private void MyWindow_Closed()
{
TextBox1.Text = a;
}
You could use a PubSubEvent which is available in Prism.Events. This will allow you to subscribe to events.
Using Prism.Events;
define your Event.
public class MyEvents : PubSubEvent<object>
{
public MyEvents();
}
In your first window or code behind
[Import]
public IEventAggregator EventAggregator
{
get;
set;
}
and you can use this property in your program to send whatever value you want to send.
For example
private void MyWindow_Closed()
{
MyEvents myEvents = EventAggregator.GetEvent<MyEvents>();
myEvents.Publish(yourvalue);
}
Once you have publised you can Subscribe to the same event in any other part of your program like this.
MyEvents myEvents = EventAggregator.GetEvent<MyEvents>();
myEvents.Subscribe(MyEventMethod, ThreadOption.UIThread, true);
and get your data here
void MyEventMethod(object obj)
{
// do wharever you want
}

How to attach help to WPF application built in VS 2008

I have one chm for my application which i want to attach with my application that is when user press F1 attached help with the project opens up.
I do not know of any in built support in WPF to display CHM files. What I do is add an InputGesture to connect F1 keystroke to Application.Help command and in the Windows CommandBindings add a handler for Application.Help Command. Here is a sample code:
<Window x:Class="WpfTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" >
<Window.InputBindings>
<KeyBinding Command="Help" Key="F1"/>
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Help" Executed="HelpExecuted" />
</Window.CommandBindings>
<Grid>
</Grid>
Here's the handler code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void HelpExecuted(object sender, ExecutedRoutedEventArgs e)
{
System.Diagnostics.Process.Start(#"C:\MyProjectPath\HelpFile.chm");
}
}
Using F1 Help (CHM format) With WPF
Based on this approach, I did the following so I could take advantage of an OnlineHelpViewModel I had that was managing the help through a RelayCommand. When F1 is pressed, with this approach, the RelayCommand on the viewmodel is invoked just as if ia ? button had been pushed. In other words,we bind F1 to the RelayCommand.
This example uses GalaSoft MvvmLight.
DependencyProperty on the MainWindow
public static DependencyProperty HelpCommandProperty = DependencyProperty.Register("HelpCommand",
typeof(RelayCommand<string>), typeof(WindowExt),
new PropertyMetadata(null));
public RelayCommand<string> HelpCommand
{
get
{
return (RelayCommand<string>)GetValue(HelpCommandProperty);
}
set
{
SetValue(HelpCommandProperty, value);
}
}
OK that holds the command
Now in the window loaded event or somewhere you like:
...
Binding b2 = new Binding();
b2.Source = ViewModelLocator.OnlineHelpViewModelStatic;
b2.Path = new PropertyPath("ShowApplicationHelpCommand");
b2.Mode = BindingMode.OneWay;
this.SetBinding(HelpCommandProperty, b2);
var kb = new KeyBinding();
kb.Key = Key.F1;
kb.Command = HelpCommand;
this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Help, HelpCommand_Executed));
OK that binds the command on the SOURCE viewmodel to this window.
Then a handler for the command on this window ( perhaps this can be inline somehow)
private void HelpCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
this.HelpCommand.Execute(HelpContextGuid);
}
and you now can call the single help command on OnlineHelpViewModel from anywhere, and it can be arbitrarily complicated too depending. Note that the DP HelpContextGuid is passed - it is up to the command to decide what to do with it but the RelayCommmand<string> wants an argument
The command itself looks like (on the SOURCE Viewmodel)
...
ShowApplicationHelpCommand = new RelayCommand<string>(
(h) => { ShowApplicationHelp(h); },
(h) => CanShowApplicationHelpCommand);
...
and method it invokes is whatever it takes to show the help,
In my case, I create a RadWindow and so on and populated it with XamlHelp using the BackSpin Software HelpLoader. The help file is generated from Word with Twister4Word.
All of this is particular to my application so you would probably do something else to make a help window. Here is the constructor:
public MigratorHelpWindow()
{
// create local resources for desingn mode, so Blend can see the viewmodels
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
App.CreateStaticResourcesForDesigner(this);
}
InitializeComponent();
if (Application.Current.MainWindow != null)
{
var thm = ThemeManager.FromName(Application.Current.FindResource("TelerikGlobalTheme").ToString() ?? "Office_Blue");
StyleManager.SetTheme(this, thm);
}
// window configuration
MaxHeight = SystemParameters.WorkArea.Height;
MaxWidth = SystemParameters.WorkArea.Width;
Binding b = new Binding();
b.Source = ViewModelLocator.OnlineHelpViewModelStatic;
b.Path = new PropertyPath("ApplicationHelpFileName");
b.Mode = BindingMode.OneWay;
this.SetBinding(ApplicationHelpFileNameProperty, b);
if (String.IsNullOrEmpty(ApplicationHelpFileName))
{
UiHelpers.ShowError("No help file is available", true);
return;
}
// LOAD YOUR HELP HERE OR WHATEVER
// LOAD YOUR HELP HERE OR WHATEVER
// LOAD YOUR HELP HERE OR WHATEVER
HelpLoader.Load(ApplicationHelpFileName);
HelpLoader.Default.Owner = this;
HelpLoader.Default.HelpLayout = HelpLayout.Standard;
HelpLoader.Default.TocContainer = _mTOC;
HelpLoader.Default.IndexContainer = _mIndex;
HelpLoader.Default.TopicContainer = _mTopic;
HelpLoader.Default.SearchContainer = _mSearch;
HelpLoader.Default.FavoritesContainer = _mFavorites;
}
You can find the BackSpin Help Authoring tool here
http://www.backspinsoftware.com/site/Default.aspx
It generates compiled help from Word documents.

How to fire MouseEnter for one object if another object has mousecapture?

I have a strange question and a vb.net 2010 and wpf4 project. I have a label that, when clicked, captures the mouse (MyLabel.captureMouse) and follows it around the screen until the mouse clicks again, at which point the object and the mousecapture is released.
However, I still need the functionality of mouseenter for another object. How do I get these two things to work together?
EDIT: There are two solutions to this, it seems, one of which I discovered. However, since Rick's should work as well (though untested because of my deadline), and I hate answering my own questions on here, I accepted his answer.
In the interim of waiting for him to comment back to a problem I had, I wound up discovering my own solution. Thus, be sure to read both my answer and Rick's.
If the other object is one of your objects, then your label and the other object can cooperate while you have the mouse captured using synthetic events. For example, in your mouse move handler, you can check Mouse.DirectlyOver to see if it is the other object and if so, do a little bookkeeping and then call RaiseEvent with either MouseEnter or MouseLeave on the other object. If you have a bunch of these objects then you just have more bookkeeping to do.
Edit:
The above refers to Mouse.DirectlyOver which specifically does not work when the mouse is captured. To make the above more concrete and to fix that error, here is a complete working example.
Here is the markup showing a canvas, a rectangle with mouse capture handling, and an ellipse with enter/leave handling:
<Grid>
<Canvas>
<Rectangle Canvas.Left="0" Canvas.Top="0"
Fill="Red" Width="100" Height="100"
MouseLeftButtonDown="Rectangle_MouseLeftButtonDown"
MouseMove="Rectangle_MouseMove"
MouseLeftButtonUp="Rectangle_MouseLeftButtonUp"/>
<Ellipse Canvas.Left="0" Canvas.Top="100"
Fill="Green" Width="100" Height="100"
MouseEnter="Ellipse_MouseEnter"
MouseLeave="Ellipse_MouseLeave">
<Ellipse.RenderTransform>
<ScaleTransform/>
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>
</Grid>
and here are the event handlers that demonstrate how to generate synthetic enter/leave events (but only for the ellipse) while the mouse is captured:
private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var capturer = sender as FrameworkElement;
capturer.CaptureMouse();
}
bool over = false;
UIElement element;
private void Rectangle_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed) return;
var capturer = sender as FrameworkElement;
var hit = VisualTreeHelper.HitTest(this, e.GetPosition(this));
if (hit == null) return;
var thisElement = hit.VisualHit as Ellipse;
var nowOver = thisElement != null;
if (nowOver) element = thisElement;
var args = new MouseEventArgs(Mouse.PrimaryDevice, 0);
if (!over && nowOver) { args.RoutedEvent = UIElement.MouseEnterEvent; element.RaiseEvent(args); }
if (over && !nowOver) { args.RoutedEvent = UIElement.MouseLeaveEvent; element.RaiseEvent(args); }
over = nowOver;
}
private void Rectangle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var capturer = sender as FrameworkElement;
capturer.ReleaseMouseCapture();
}
private void Ellipse_MouseEnter(object sender, MouseEventArgs e)
{
Debug.WriteLine("MouseEnter");
}
private void Ellipse_MouseLeave(object sender, MouseEventArgs e)
{
Debug.WriteLine("MouseLeave");
}
If you run the demo under the debugger you'lll see that the enter/leave handlers are called whether the mouse is captured or not.
In my experience using Capturing is not the way to handle drag and drop (if you are implementing drag and drop) precisely because of the reason you state here.
I solve this situation by keeping track of the mouse button and movement and translating the control along.
It is also possible to use the Blend Drag behavior.
Since the code I was using to simulate the "dragging" of an object involved getting the mouse position anyway, I capitalized on the opportunity and wrote a code that checked whether or not the mouse's position on the canvas was mathematically within the boundaries of each of the objects I needed mouseenter/mouseleave for. This worked especially well since the objects I needed the mouseover/mouseleave for never changed positions.
Here is the trimmed version of my final code.
'Declare the left, right, top, and bottom boundaries of each of the "drop-spot" objects in relation to the canvas. This could also be determined programamtically for objects that change position.
Private Tile1PosL As Double = 55
Private Tile1PosT As Double = 30
Private Tile2PosL As Double = 164
Private Tile2PosT As Double = 69
Private Tile3PosL As Double = 282
Private Tile3PosT As Double = 41
Private Tile4PosL As Double = 405
Private Tile4PosT As Double = 69
Private Tile5PosL As Double = 514
Private Tile5PosT As Double = 12
Private Sub Tile1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Tile1.MouseMove
If IsDragging1 = True Then
'My dragging functions go here.
'Get the mouse position on the canvas (canv).
Dim MousePosX As Double = e.GetPosition(canv).X
Dim MousePosY As Double = e.GetPosition(canv).Y
'Check to see if the mouse is within the boundaries of any of the "drop-spot" objects (Hole1, Hole2, Hole3, Hole4, Hole5).
If MousePosX > Hole1L And MousePosX < Hole1R And MousePosY > Hole1T And MousePosY < Hole1B Then
'Call the subroutine containing the code that would ordinarily go in "mouseenter".
Hole1_TileEnter()
ElseIf MousePosX > Hole2L And MousePosX < Hole2R And MousePosY > Hole2T And MousePosY < Hole2B Then
'Call the subroutine containing the code that would ordinarily go in "mouseenter".
Hole2_TileEnter()
ElseIf MousePosX > Hole3L And MousePosX < Hole3R And MousePosY > Hole3T And MousePosY < Hole3B Then
'Call the subroutine containing the code that would ordinarily go in "mouseenter".
Hole3_TileEnter()
ElseIf MousePosX > Hole4L And MousePosX < Hole4R And MousePosY > Hole4T And MousePosY < Hole4B Then
'Call the subroutine containing the code that would ordinarily go in "mouseenter".
Hole4_TileEnter()
ElseIf MousePosX > Hole5L And MousePosX < Hole5R And MousePosY > Hole5T And MousePosY < Hole5B Then
'Call the subroutine containing the code that would ordinarily go in "mouseenter".
Hole5_TileEnter()
Else
'If the mouse is not within any of the "drop-spot" objects, call the subroutine containing the code that would ordinarily go in each object's "mouseleave". NOTE: This code contains statements that determine that the mouse had been inside one of the "drop-spots" before actually triggering the rest of its code.
Hole_TileLeave()
End If
End If
End If
'This subroutine, with minor modifications, works for my Tile2, Tile3, Tile4, and Tile5 as well.
End Sub

Create Video from ImageSource

Is there any easy way of adding ImageSources to a stack and create a video from it?
I already did such a class. I only have to submit my "ImageInfo" which is a system.DrawingBitmap. This can be created easy by using the following code:
Public Function WpfBitmapSourceToBitmap(ByVal source As BitmapSource) As System.Drawing.Bitmap
If source Is Nothing Then Return Nothing
Dim bmp As New System.Drawing.Bitmap(source.PixelWidth, source.PixelHeight, System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
Dim data As System.Drawing.Imaging.BitmapData = bmp.LockBits(New System.Drawing.Rectangle(System.Drawing.Point.Empty, bmp.Size), System.Drawing.Imaging.ImageLockMode.[WriteOnly], System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
source.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride)
bmp.UnlockBits(data)
Return bmp
End Function
Then I did a AviClass to add frames to it and store it as a AVI file with preselected Codec (for example XVid MPEG4)
Public Class clsAviWriter
Inherits MAINInterface.TB.Imaging.Pia7.clsDspTemplate
Private cAvi As AviReaderWriter.AviFile.AviManager
Private AviStream As AviReaderWriter.AviFile.VideoStream
Private AudioStream As AviReaderWriter.AviFile.AudioStream
Private cFps As clsTbQueue
Private OldFpsDate As Date = Now
''' <summary>
''' The image object to paint graphical objects on it
''' </summary>
''' <value>descriptor of the image</value>
Public Overrides Property ImageInfo() As MAINInterface.TB.Imaging.Pia7.clsImageInfo
Get
Return Me._ImageInfo
End Get
Set(ByVal value As MAINInterface.TB.Imaging.Pia7.clsImageInfo)
Me._ImageInfo = value
Call WriteFrame()
Call Me.OnPropertyChanged(Me.Guid)
End Set
End Property
Private Sub WriteFrame()
Dim D As Date = Now
Dim Fps As Single
Me.cFps.Values.Add(D.Subtract(Me.OldFpsDate).Ticks)
Me.OldFpsDate = D
Me.cFps.Trim()
Fps = 1000 / New TimeSpan(Me.cFps.Average).TotalMilliseconds
Me.cFps.BufferSize = TB.Math.myTrim(Fps * 1, 1, 1000)
If Me.AviStream IsNot Nothing Then
Me.AviStream.AddFrame(Me._ImageInfo.Image.Clone)
End If
End Sub
Public Sub New()
Me.ClassDescription = "Write images into an avi file"
Me.cFps = New clsTbQueue(10)
End Sub
Private Sub InitializeAvi()
Dim W As String
Dim Fps As Single
Dim di As New IO.DirectoryInfo(TB.SystemMain.AppPath & "Avi\")
TB.FileSystem.CreateDirectories(di)
W = IO.Path.Combine(di.FullName, "Record_" & Now.Ticks.ToString("0") & ".avi")
Me.cAvi = New AviReaderWriter.AviFile.AviManager(W, False)
Dim Opts As New AviReaderWriter.AviFile.Avi.AVICOMPRESSOPTIONS
Opts.fccType = 0
Opts.fccHandler = 1684633208
Opts.dwKeyFrameEvery = 0
Opts.dwQuality = 0 '0 ... 10000
Opts.dwFlags = 8 'AVICOMRPESSF_KEYFRAMES = 4
Opts.dwBytesPerSecond = 0
Opts.lpFormat = 0
Opts.lpParms = New IntPtr(0)
Opts.cbParms = 3532
Opts.dwInterleaveEvery = 0
Fps = 1000 / New TimeSpan(Me.cFps.Average).TotalMilliseconds
'Dim bm1 As Bitmap
'bm1 = TB.Imaging.CreateReScaledImage(Me.pic.Image, New Size(Me.pic.Image.Width, Me.pic.Image.Height), False)
Me.AviStream = cAvi.AddVideoStream(Opts, Math.Floor(TB.Math.myTrim(Fps, 1, 50)), Me._ImageInfo.Image.Clone)
End Sub
Public Overrides Property Run() As Boolean
Get
Return Me._Run
End Get
Set(ByVal value As Boolean)
If Me._Run <> value Then
Me._Run = value
If Me._Run = True Then
Call InitializeAvi()
Else
If Me.cAvi IsNot Nothing Then
Me.cAvi.Close()
Me.cAvi = Nothing
Me.AviStream = Nothing
End If
End If
Call Me.OnPropertyChanged(Me.Guid)
End If
End Set
End Property
End Class
For more codes look here: http://www.wischik.com/lu/programmer/avi_utils.html and MSDN or http://www.codeproject.com/KB/audio-video/avigenerator.aspx
I've posted the sourcecode to show how such a sequence can looks like (code above need some more references which are not public available). You can see that you just need to initialize, add frames, store the FPS value and safe it to harddisk.
Also if wanted, you can search for DirectShow to see how all works.
You can use
http://joshsmithonwpf.wordpress.com/2008/04/23/good-old-fashion-image-animations-in-wpf/
as an example. Afterwards you can use a screen capture program like snagit or microsoft expression encoder pro to capture it as a video
Josh Smith's blog pointed by Raj here (http://joshsmithonwpf.wordpress.com/2008/04/23/good-old-fashion-image-animations-in-wpf/) is a good example of showing images from a folder in the WPF app.
Once this is working you can look at Saveen Reddy's blog to convert app to video
http://blogs.msdn.com/b/saveenr/archive/2008/09/22/wpf-xaml-saving-an-animation-as-an-avi-video-file.aspx
Use this library avifilewrapper search for the sample code on how to create an avi from bitmaps. This article explains how you can render your visuals to bitmaps. I don't think it will get any easier than that.
Since WPF does not include video encoding libraries, you'll need to lean on an external one to do the encoding. This blog post describes how you can use Windows Media Encoder to do so. Alternatively, you could bundle something like mencoder with your app and start it as an external process that you control and monitor from your app.

Resources