Display progress during matrix loop? - c#

I'm making a chessboard, I want to see progress while it's done.
Chessboard is not classical, it contains millions of fields, the process of creation itself takes time. Sometimes the creation process takes up to 2 minutes. I want to visually see when the process itself will be over. It does not have to be a progress bar, it can be any control that will not slow down the process itself.
When I use Progress.Dispatcher.Invoke (()... I actually slow down the creation process and it takes 5 times longer than usual. When I use BackgroundWorker and ReportProgress ... I also slow down the creation process and it takes 5 to 8 times more than usual.
I just want to show the user progress by using any control or class that will not slow down the process. Some idea?
Rectangle[,] square = new Rectangle[x, x];
for (int row = 0; row < x; row++)
for (int col = 0; col < x; col++)
{
square[row, col] = new Rectangle()
{
Height = squareSize,
Width = squareSize
};
Grid.SetColumn(square[row, col], col);
Grid.SetRow(square[row, col], row);
if ((row + col) % 2 == 0)
{
square[row, col].Fill = new SolidColorBrush(System.Windows.Media.Color.FromRgb(233, 223, 191));
}
else
{
square[row, col].Fill = new SolidColorBrush(System.Windows.Media.Color.FromRgb(112, 42, 44));
}
LayoutRoot.Children.Add(square[row, col]);
// Watch process of creation in real time
if (cbCreationProcess.IsChecked == true)
Progress.Dispatcher.Invoke(() => Progress.Value = x, DispatcherPriority.Background);
}

This solution has worked for me in the past.
ProgressWindowControl.xaml
<Window
x:Class="YourNamespace.ProgressWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
WindowStartupLocation="CenterScreen" ShowInTaskbar="False" ResizeMode="NoResize"
SizeToContent="WidthAndHeight" WindowStyle="None"
mc:Ignorable="d">
<Window.Style>
<Style TargetType="Window">
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="Background" Value="#00FFFFFF"/>
</Style>
</Window.Style>
<Grid>
<Grid Width="450" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0">
<Grid x:Name="Back">
<Border Background="Black" CornerRadius="3" Opacity="0.15"/>
<Border CornerRadius="2" Margin="1" Background="White"/>
</Grid>
<Grid x:Name="Content_Area" Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock x:Name="Info" TextWrapping="Wrap"
Text="{Binding Path=State,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Grid.Row="0" Margin="12,12,12,0" Foreground="#FF2D2D2D"/>
<ProgressBar Height="12"
Grid.Row="1"
Margin="12"
IsIndeterminate="{Binding Path=IsIndeterminate,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Value="{Binding Path=Progress,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Maximum="{Binding Path=MaxProgress,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<Button x:Name="uxCancelBtn" Grid.Row="2" Height="22" Width="85" HorizontalAlignment="Right" Margin="0 0 12 0"
Click="CancelButton_Click" IsEnabled="False" Content="{x:Static resx:Strings.Cancel}">
</Button>
</Grid>
</Grid>
</Grid>
</Window>
ProgressWindowControl.cs
public sealed partial class ProgressWindowControl : Window
{
public static readonly DependencyProperty ProgressProperty =
DependencyProperty.Register("Progress", typeof(double), typeof(ProgressWindowControl), new PropertyMetadata(0d));
public static readonly DependencyProperty MaxProgressProperty =
DependencyProperty.Register("MaxProgress", typeof(double), typeof(ProgressWindowControl), new PropertyMetadata(100d));
public static readonly DependencyProperty IsIndeterminateProperty =
DependencyProperty.Register("IsIndeterminate", typeof(bool), typeof(ProgressWindowControl), new PropertyMetadata(true));
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State", typeof(string), typeof(ProgressWindowControl), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty IsCancelAllowedProperty =
DependencyProperty.Register("IsCancelAllowed", typeof(bool), typeof(ProgressWindowControl), new PropertyMetadata(false));
private ProgressWindowControl()
{
InitializeComponent();
}
public double Progress
{
get
{
return (double)GetValue(ProgressProperty);
}
set
{
SetValue(ProgressProperty, value);
}
}
public double MaxProgress
{
get
{
return (double)GetValue(MaxProgressProperty);
}
set
{
SetValue(MaxProgressProperty, value);
}
}
public bool IsIndeterminate
{
get
{
return (bool)GetValue(IsIndeterminateProperty);
}
set
{
SetValue(IsIndeterminateProperty, value);
}
}
public string State
{
get
{
return (string)GetValue(StateProperty);
}
set
{
SetValue(StateProperty, value);
}
}
public Action OnProgressWindowCancel { get; set; }
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
if (OnProgressWindowCancel != null)
{
uxCancelBtn.IsEnabled = false;
uxCancelBtn.Content = Strings.Cancelling;
OnProgressWindowCancel();
}
}
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);
private const int GWL_HWNDPARENT = -8;
private static ProgressWindowControl _progressWindowControl;
private static bool _isVisible;
private static Window _owner;
private static ResizeMode? _ownerResizeMode;
private static bool _ownerIsHitTestVisible;
private static bool _ownerFocusable;
public static void ShowProgressWindow(Window owner = null)
{
if (!_isVisible)
{
IntPtr ownerHandle = IntPtr.Zero;
if (owner != null)
{
_owner = owner;
ownerHandle = GetHandler(_owner);
//Block owner window input while the progress bar is opened
_ownerResizeMode = _owner.ResizeMode;
_ownerIsHitTestVisible = _owner.IsHitTestVisible;
_ownerFocusable = _owner.Focusable;
_owner.ResizeMode = ResizeMode.NoResize;
_owner.IsHitTestVisible = false;
_owner.Focusable = false;
_owner.PreviewKeyDown += Owner_PreviewKeyDown;
_owner.PreviewMouseDown += Owner_PreviewMouseDown;
_owner.Closing += Owner_Closing;
}
//Run window in its own thread
Thread thread = new Thread(new ThreadStart(() =>
{
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
_progressWindowControl = new ProgressWindowControl();
// Shutdown the dispatcher when the window closes
_progressWindowControl.Closed += (s, e) =>
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
// When the progress window has loaded, if an owner has been specified, attach it to the window, otherwise set Topmost = true
ProgressWindowControl._progressWindowControl.Loaded += (s, e) =>
{
if (owner != null)
{
IntPtr ownedWindowHandle = GetHandler(_progressWindowControl);
SetOwnerWindowMultithread(ownedWindowHandle, ownerHandle);
}
else
{
_progressWindowControl.Topmost = true;
}
};
_progressWindowControl.Show();
_isVisible = true;
System.Windows.Threading.Dispatcher.Run();
}));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
}
private static void Owner_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
}
private static void Owner_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
}
private static void Owner_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
e.Handled = true;
}
private static void SetOwnerWindowMultithread(IntPtr windowHandleOwned, IntPtr intPtrOwner)
{
if (windowHandleOwned != IntPtr.Zero && intPtrOwner != IntPtr.Zero)
{
SetWindowLong(windowHandleOwned, GWL_HWNDPARENT, intPtrOwner.ToInt32());
}
}
private static IntPtr GetHandler(Window window)
{
var interop = new WindowInteropHelper(window);
return interop.Handle;
}
public static void CloseProgressWindow()
{
if (_progressWindowControl != null && _isVisible)
{
if (_progressWindowControl.Dispatcher.CheckAccess())
{
_progressWindowControl.Close();
}
else
{
_progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
new ThreadStart(_progressWindowControl.Close));
}
if (_owner != null)
{
//Unblock owner input
_owner.ResizeMode = _ownerResizeMode ?? ResizeMode.CanResize;
_owner.IsHitTestVisible = _ownerIsHitTestVisible;
_owner.Focusable = _ownerFocusable;
_owner.PreviewKeyDown -= Owner_PreviewKeyDown;
_owner.PreviewMouseDown -= Owner_PreviewMouseDown;
_owner.Closing -= Owner_Closing;
}
//Reset fields
_ownerResizeMode = null;
_ownerIsHitTestVisible = false;
_ownerFocusable = false;
_progressWindowControl = null;
_owner = null;
_isVisible = false;
}
}
public static void SetProgress(double progress, double maxProgress)
{
if (_progressWindowControl != null)
{
if (_progressWindowControl.Dispatcher.CheckAccess())
{
_progressWindowControl.IsIndeterminate = false;
_progressWindowControl.Progress = progress;
_progressWindowControl.MaxProgress = maxProgress;
}
else
{
_progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
new ThreadStart(() =>
{
_progressWindowControl.IsIndeterminate = false;
_progressWindowControl.Progress = progress;
_progressWindowControl.MaxProgress = maxProgress;
}));
}
}
}
public static void SetIsIndeterminate(bool isIndeterminate)
{
if (_progressWindowControl != null)
{
if (_progressWindowControl.Dispatcher.CheckAccess())
{
_progressWindowControl.IsIndeterminate = isIndeterminate;
}
else
{
_progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
new ThreadStart(() =>
{
_progressWindowControl.IsIndeterminate = isIndeterminate;
}));
}
}
}
public static void SetState(string state)
{
if (_progressWindowControl != null)
{
if (_progressWindowControl.Dispatcher.CheckAccess())
{
_progressWindowControl.State = state;
}
else
{
_progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
new ThreadStart(() =>
{
_progressWindowControl.State = state;
}));
}
}
}
public static void SetIsCancelAllowed(bool isCancelAllowed, Action progressWindowCancel)
{
if (_progressWindowControl != null)
{
if (_progressWindowControl.Dispatcher.CheckAccess())
{
_progressWindowControl.OnProgressWindowCancel = progressWindowCancel;
_progressWindowControl.uxCancelBtn.IsEnabled = isCancelAllowed;
_progressWindowControl.uxCancelBtn.Content = Strings.Cancel;
}
else
{
_progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
new ThreadStart(() =>
{
_progressWindowControl.OnProgressWindowCancel = progressWindowCancel;
_progressWindowControl.uxCancelBtn.IsEnabled = isCancelAllowed;
_progressWindowControl.uxCancelBtn.Content = Strings.Cancel;
}));
}
}
}
}
A helper class to open the window:
public static class ProgressWindowHelper
{
public static void Show(Window owner = null)
{
ProgressWindowControl.ShowProgressWindow(owner);
}
public static void Close()
{
ProgressWindowControl.CloseProgressWindow();
}
public static void SetProgress(double progress, double maxProgress)
{
ProgressWindowControl.SetProgress(progress, maxProgress);
}
public static void SetIsIndeterminate(bool isIndeterminate)
{
ProgressWindowControl.SetIsIndeterminate(isIndeterminate);
}
public static void SetState(string state)
{
ProgressWindowControl.SetState(state);
}
public static void SetIsCancelAllowed(bool isCancelAllowed, Action progressWindowCancel)
{
ProgressWindowControl.SetIsCancelAllowed(isCancelAllowed, progressWindowCancel);
}
}
A service so you can use Dependency Injection (I didn't include the interface, just create one as needed):
public class ProgressWindowService : IProgressWindowService
{
public void Show(Window owner = null)
{
ProgressWindowHelper.Show(owner);
}
public void Close()
{
ProgressWindowHelper.Close();
}
public void SetProgress(double progress, double maxProgress)
{
ProgressWindowHelper.SetProgress(progress, maxProgress);
}
public void SetIsIndeterminate(bool isIndeterminate)
{
ProgressWindowHelper.SetIsIndeterminate(isIndeterminate);
}
public void SetState(string state)
{
ProgressWindowHelper.SetState(state);
}
public void SetIsCancelAllowed(bool isCancelAllowed, Action progressWindowCancel)
{
ProgressWindowHelper.SetIsCancelAllowed(isCancelAllowed, progressWindowCancel);
}
}
And finally in your ViewModel (assuming you have injected the service):
ProgressBarService.SetProgress(current, total);
ProgressBarService.SetState(state);
ProgressBarService.Show();
You can also pass a window to the Show method and then the window will be attached to it and input will be blocked while the progress window is shown, if no window is provided, the progress bar is shown on top of any other window (TopMost=true):
ProgressBarService.Show(YourWindow);
You could also use a messenger to trigger the progress window.
EDIT
Removed DevExpress dependency.

obviously any addition of functionality is going to slow it down as you're well aware.
what I would do if I was told to use the above code is likely create a timer and poke into where-ever you have your current row and col increments and its up to you what you do with your calculation.
All you would have to do is handle the Timer.Elapsed event (whatever kind of timer) and calculate/report current progress in percent or such.
Otherwise, you could write up a separate thread and run it in a loop that sleeps N-milloseconds until drawing is complete—essentially tandem with the idea of using a timer. Generally, this is what we would do to report progress on a playing mp3 or some such object manipulated on a separate thread.
there is a lot of room for you to be creative here and milk some wisdom out of this scenario to optimize results but before considering such...
bare in mind that WPF is a beast when it comes to how it deals with drawing things since it relies heavily on the video hardware, memory, etc... I tend to think of WPF as like html for OpenGL or DirectX. I hope your GPU has a good connection to the heat-sync and doesn't get too hot and/or that you're not working on a laptop with an embedded GPU. I'm being aggressive here, but only because I've blown pleanty of hardware over the years writing software in the write/compile/run/and-repeat cycle. I'd have gotten much more life out of lots of hardware if I played a bit safer. not to mention recent years are getting harder and harder on our hardware.
for the sake of being as creative as possible here
since we know how many squares there are and can be sure of the resulting grid's dimensions, have you thought rendering a custom pattern as a background and explicitly setting the size of the grid? Once that's done, you could be creative when it comes to further populating needed or spin up some interesting transitions, etc
learning/using System.Threading.Thread(ThreadStart) will enable you to set process-priority to a higher value than standard defaults — as opposed to using BackgroundWorker or some other such... but I haven't quite wrapped my mind around an implementation with such as this.
build up some objects outside the UI such as a List<> and render at the end of a row, column or perhaps every N incremenet
we know that we have x*x boxes in the end, so if we're explicit about the size, there is so much room for creativity here. I hadn't tinkered with WPF in a while, but this would be a good case for studying and looking at different ways to lay such a thing out (eg: CustomLayoutPanel tut). If you're stuck on the notion of a Grid, there is only so much you can do---I would instinctively be working with Drawing methods for doing this, and would likely come up with a way to draw only what is visible on the screen as needed... or
generally #dymanoid's response is poking you into the correct direction but it breaks the logic of the question being asked, therefore overshoots the scope of the question... but we want to enlighten here and generally yes any rendering process stuff in WPF should be thought out using existing optimization strategies such as MVVM and styles/templates/transitions.

Related

Blackmagic decklink CapturePreviewCSharp fails to capture video

I have run the project CapturePreviewCSharp provided as a sample. However, it seems to be able to acquire the device status, etc., but the image is not output to the preview screen.
There are no errors, etc.
It seems that the rendering to D3DImage is not working, but I don't know how to solve this problem.
Does anyone else have a similar problem?
I have asked on the Blackmagic forum but have not gotten an answer.
Development environment.
Windows 11.
Visual Studio 2022
NET framework 4.8
DeckLink mini-recorder 4K
Here is the code on the Xaml side
<Window x:Class="CapturePreviewCSharp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:System.Windows.Interop;assembly=PresentationCore"
<!--excerpt code-->
<GroupBox x:Name="groupBoxPreview" Header="Preview" >
<Grid x:Name="gridPreview" Margin="5">
<Image x:Name="imagePreview">
<Image.Source>
<i:D3DImage x:Name="d3dPreview" />
</Image.Source>
</Image>
</Grid>
</GroupBox>
</DockPanel>
</Window>
Code to output images to D3DImage.
public void RenderD3DImage(object sender, EventArgs e)
{
UpdateUIElement(d3dPreview, new Action(() =>
{
var actualWidth = gridPreview.RenderSize.Width;
var actualHeight = gridPreview.RenderSize.Height;
if (d3dPreview.IsFrontBufferAvailable)
{
IntPtr surface = IntPtr.Zero;
if (actualWidth > 0 && actualHeight > 0)
{
new MTAAction(() =>
{
m_previewCallback.PreviewHelper.SetSurfaceSize((uint)actualWidth, (uint)actualHeight);
m_previewCallback.PreviewHelper.GetBackBuffer(out surface);
});
}
if (surface != IntPtr.Zero)
{
d3dPreview.Lock();
d3dPreview.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface);
new MTAAction(() => m_previewCallback.PreviewHelper.Render());
d3dPreview.AddDirtyRect(new Int32Rect(0, 0, d3dPreview.PixelWidth, d3dPreview.PixelHeight));
d3dPreview.Unlock();
}
}
}));
}
MTAAction class.
namespace CapturePreviewCSharp
{
class MTAAction
{
private ManualResetEvent doneEvent;
private readonly Action action;
public MTAAction(Action action)
{
this.action = action;
doneEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(MTAActionThreadPoolCallback, 0);
doneEvent.WaitOne();
}
public void MTAActionThreadPoolCallback(Object threadContext)
{
action();
doneEvent.Set();
}
}
class MTAFunc<T> where T : struct
{
private T value;
private readonly Func<T> getValueFunc;
private ManualResetEvent doneEvent;
public MTAFunc(Func<T> func)
{
value = default(T);
getValueFunc = func;
doneEvent = new ManualResetEvent(false);
}
public T Value
{
get
{
ThreadPool.QueueUserWorkItem(MTAFuncThreadPoolCallback, 0);
doneEvent.WaitOne();
return (T)value;
}
}
public void MTAFuncThreadPoolCallback(Object threadContext)
{
value = getValueFunc();
doneEvent.Set();
}
}
}
Thank you.

Slider do not work after changing track NAudio

I have slider as trackbar for soundtrack timeline.Soundtrack play from web using NAudion. All code using from NAudio WPF example.I changed only accessibility modifiers.First time when I start play first track all works good.But if I change to next track, slider still in start.And changing only if I click Pause then Play.
For fully understand:
First track - slider works and moves.
Change to the next track - slider at the beginning and doesn't move. But it begins to move after pressing Pause, then Play. It immediately moves to the place where the playback is at the moment and continues normal operation. And so with each following track.
Code of VM for PlayerUserControl:
public class AudioControlVM : ViewModelBase, IDisposable
{
private AudioModel _currentSong;
public AudioModel CurrentSong { get { return _currentSong; } set { _currentSong = value; RaisePropertyChanged("CurrentSong"); } }
private string inputPath, songName;
private string defaultDecompressionFormat;
public IWavePlayer wavePlayer { get; set; }
private WaveStream reader;
public RelayCommand PlayCommand { get; set; }
public RelayCommand PauseCommand { get; set; }
public RelayCommand StopCommand { get; set; }
public DispatcherTimer timer = new DispatcherTimer();
private double sliderPosition;
private readonly ObservableCollection<string> inputPathHistory;
private string lastPlayed;
public AudioControlVM()
{
inputPathHistory = new ObservableCollection<string>();
PlayCommand = new RelayCommand(() => Play());
PauseCommand = new RelayCommand(() => Pause());
StopCommand = new RelayCommand(Stop, () => !IsStopped);
timer.Interval = TimeSpan.FromMilliseconds(500);
timer.Tick += TimerOnTick;
}
public bool IsPlaying => wavePlayer != null && wavePlayer.PlaybackState == PlaybackState.Playing;
public bool IsStopped => wavePlayer == null || wavePlayer.PlaybackState == PlaybackState.Stopped;
public IEnumerable<string> InputPathHistory => inputPathHistory;
const double SliderMax = 10.0;
private void TimerOnTick(object sender, EventArgs eventArgs)
{
if (reader != null)
{
sliderPosition = reader.Position * SliderMax / reader.Length;
RaisePropertyChanged("SliderPosition");
}
}
public double SliderPosition
{
get => sliderPosition;
set
{
if (sliderPosition != value)
{
sliderPosition = value;
if (reader != null)
{
var pos = (long)(reader.Length * sliderPosition / SliderMax);
reader.Position = pos; // media foundation will worry about block align for us
}
RaisePropertyChanged("SliderPosition");
}
}
}
private bool TryOpenInputFile(string file)
{
bool isValid = false;
try
{
using (var tempReader = new MediaFoundationReader(file))
{
DefaultDecompressionFormat = tempReader.WaveFormat.ToString();
InputPath = file;
isValid = true;
}
}
catch (Exception e)
{
}
return isValid;
}
public string DefaultDecompressionFormat
{
get => defaultDecompressionFormat;
set
{
defaultDecompressionFormat = value;
RaisePropertyChanged("DefaultDecompressionFormat");
}
}
public string SongName { get => songName; set
{
songName = value;
RaisePropertyChanged("SongName");
} }
public string InputPath
{
get => inputPath;
set
{
if (inputPath != value)
{
inputPath = value;
AddToHistory(value);
RaisePropertyChanged("InputPath");
}
}
}
private void AddToHistory(string value)
{
if (!inputPathHistory.Contains(value))
{
inputPathHistory.Add(value);
}
}
public void Stop()
{
if (wavePlayer != null)
{
wavePlayer.Stop();
}
}
public void Pause()
{
if (wavePlayer != null)
{
wavePlayer.Pause();
RaisePropertyChanged("IsPlaying");
RaisePropertyChanged("IsStopped");
}
}
public void Play()
{
if (String.IsNullOrEmpty(InputPath))
{
return;
}
if (wavePlayer == null)
{
CreatePlayer();
}
if (lastPlayed != inputPath && reader != null)
{
reader.Dispose();
reader = null;
}
if (reader == null)
{
reader = new MediaFoundationReader(inputPath);
lastPlayed = inputPath;
wavePlayer.Init(reader);
}
wavePlayer.Play();
RaisePropertyChanged("IsPlaying");
RaisePropertyChanged("IsStopped");
timer.Start();
}
private void CreatePlayer()
{
wavePlayer = new WaveOutEvent();
wavePlayer.PlaybackStopped += WavePlayerOnPlaybackStopped;
RaisePropertyChanged("wavePlayer");
}
private void WavePlayerOnPlaybackStopped(object sender, StoppedEventArgs stoppedEventArgs)
{
if (reader != null)
{
SliderPosition = 0;
//reader.Position = 0;
timer.Stop();
}
if (stoppedEventArgs.Exception != null)
{
}
RaisePropertyChanged("IsPlaying");
RaisePropertyChanged("IsStopped");
}
public void PlayFromUrl(string url, string songname)
{
Stop();
inputPath = url;
SongName = songname;
Play();
}
public void Dispose()
{
wavePlayer?.Dispose();
reader?.Dispose();
}
}
XAML of player:
<Grid>
<StackPanel Orientation="Horizontal">
<Button Content="Play" Command="{Binding PlayCommand}" VerticalAlignment="Center" Width="75" />
<Button Content="Pause" Command="{Binding PauseCommand}" VerticalAlignment="Center" Width="75" />
<Button Content="Stop" Command="{Binding PlayCommand}" VerticalAlignment="Center" Width="75" />
<Slider VerticalAlignment="Center" Value="{Binding SliderPosition, Mode=TwoWay}" Maximum="10" Width="400" />
<TextBlock Text="{Binding SongName, FallbackValue=Test}" Foreground="White"/>
</StackPanel>
</Grid>
</UserControl>
VM code that sends data for a new track:
public class AudioModel
{
public string Artist { get; set; }
public string SongName { get; set; }
public int Duration { get; set; }
public string URL { get; set; }
public RelayCommand PlayThisAudioCommand
{
get;
private set;
}
public AudioModel()
{
PlayThisAudioCommand = new RelayCommand(() => PlayThis());
}
private void PlayThis()
{
if (URL != null)
{
TestVM.AudioConrol.PlayFromUrl(URL, SongName);
}
else;
}
}
It looks like you may have a multi-threading issue with your timer. The sequence of events appears to be:
First Track
PlayFromUrl() calls Play() which starts the file playing, and starts the timer.
Slider updates as expected
Second Track
When you call PlayFromUrl() it:
Calls Stop() (which stops the wavePlayer, and stops the timer)
Calls Play() (which starts the wavePlayer, and starts the timer)
Then the wavePlayer.PlaybackStopped event is raised (due to your earlier call to wavePlayer.Stop()), which calls WavePlayerOnPlaybackStopped(), which stops the timer.
The important point here is the order that Play() and WavePlayerOnPlaybackStopped() are called. It's very likely that the events are happening in the order above - as the wavePlayer raises the PlaybackStopped event on another thread.
In short - that WavePlayerOnPlaybackStopped() is stopping your timer after Play() started it, which is why your slider isn't updating. Pressing Pause then Play will restart the timer, which is why the slider begins updating after pausing.
You could test this by temporarily commenting out the code in WavePlayerOnPlaybackStopped(), which should fix the issue - although your slider will not reset to zero when the track reaches the end or stops.
NOTE: The cause of the delay between calling wavePlayer.Stop() and the wavePlayer.PlaybackStopped event being raised is due to nAudio using a dedicated thread to handle playback. When you call Stop(), it must finish processing the current audio buffer before actually stopping - which in most cases will result in a delay of a few milliseconds.
You can see this in action in the WaveOutEvent's DoPlayback method: https://github.com/naudio/NAudio/blob/master/NAudio/Wave/WaveOutputs/WaveOutEvent.cs#L147

DateTime.Now based timer not tracking correctly over multiple instances

this is my first proper C# application that I wrote to help me at work (I'm on helpdesk for an MSP with a passing interest in scripting and code) and I'm using UWP just to make it look pretty without much effort. Our time tracking software is a web service written in ASP.Net so generally the built in timer is fine but it won't survive a browser refresh, so I wrote my own that fits into the format that we need for our tickets.
I have taken some code from other Stack questions and my dad (A C# framework dev for a multinational) helped re-write some of the timer code so it wasn't using stopwatch. He just isn't available to fix this issue at the moment. I do understand how it works now, just not how to debug the issue I'm getting.
It supports multiple timers running at the same time and creating a new timer auto-pauses all others. It handles two time formats, minutes and decimal hours, so that will explain some of the properties you see in the code.
My issue is that when I add a new timer, it pauses all others, but then when I press start on an older timer (Returning back to an earlier ticket) the time instantly jumps up to how long the new timer was running for, with about 10% difference (It's never exactly how long it was running).
This is the class that tracks notes and the current time (Tidied up a bit for neatness):
public sealed class JobTimer:INotifyPropertyChanged
{
private DateTime _created; // When the timer was created
private DateTime _started; // When it was most recently started
private TimeSpan _offset; // The saved value to offset the currently running timer
Timer _swTimer; // The actual tick that updates the screen
public JobTimer() : this(TimeSpan.Zero)
{ }
public JobTimer(TimeSpan offset)
{
_offset = offset;
_created = DateTime.Now;
IsNotLocked = true;
}
// Time in seconds
public string TimeMin => string.Format("{0:00}:{1:00}:{2:00}", ElapsedTime.Hours, ElapsedTime.Minutes, ElapsedTime.Seconds);
// Time in decimal hours
public string TimeDec => string.Format("{0}", 0.1 * Math.Ceiling(10 * ElapsedTime.TotalHours));
public DateTime Created => _created;
public TimeSpan ElapsedTime => GetElapsed();
public void Start()
{
_started = DateTime.Now;
_swTimer = new Timer(TimerChanged, null, 0, 1000);
NotifyPropertyChanged("IsRunning");
}
public void Stop()
{
if (_swTimer != null)
{
_swTimer.Dispose();
_swTimer = null;
}
_offset = _offset.Add(DateTime.Now.Subtract(_started));
NotifyPropertyChanged("IsRunning");
}
private TimeSpan GetElapsed()
{
// This was made as part of my own debugging, the ElaspsedTime property used to just be the if return
if (IsRunning)
{
return _offset.Add(DateTime.Now.Subtract(_started));
}
else
{
return _offset;
}
}
// Updates the UI
private void TimerChanged(object state)
{
NotifyPropertyChanged("TimeDec");
NotifyPropertyChanged("TimeMin");
}
public bool IsRunning
{
get { return _swTimer != null; }
}
public void ToggleRunning()
{
if (IsRunning)
{
Stop();
}
else
{
Start();
}
}
}
This goes into the ViewModel:
public class JobListViewModel
{
private readonly ObservableCollection<JobTimer> _list = new ObservableCollection<JobTimer>();
public ObservableCollection<JobTimer> JobTimers => _list;
public JobListViewModel()
{
AddTimer();
}
public void AddTimer()
{
JobTimer t = new JobTimer();
JobTimers.Add(t);
t.Start();
}
public void PauseAll()
{
foreach(JobTimer timer in JobTimers)
{
timer.Stop();
}
}
// Other functions unrelated
}
And this is the UI button click that adds a new timer
private void AddTimer_Click(object sender, RoutedEventArgs e)
{
// Create JobTimer
ViewModel.PauseAll();
ViewModel.AddTimer();
// Scroll to newly created timer
JobTimer lastTimer = ViewModel.JobTimers.Last();
viewTimers.UpdateLayout();
viewTimers.ScrollIntoView(lastTimer);
}
I realise it's a lot of code to dump into a post but I can't pinpoint where the issue is being caused. I was able to find that something alters the offset when I hit the AddTimer button whether the existing timer is running or not, but I can't find what's altering it.
After building enough other code to support the code you posted, I was able to reproduce your problem.
The issue in your code is that you unconditionally call the Stop() method, whether the timer is already stopped or not. And the Stop() method unconditionally resets the _offset field, whether or not the timer is already running. So, if you add a timer when any other timer is already stopped, its _offset value is incorrectly reset.
IMHO, the right fix is for the Start() and Stop() methods to only perform their work when the timer is in the appropriate state to be started or stopped. I.e. to check the IsRunning property before actually doing the operation.
See below for an actual Minimal, Complete, and Verifiable version of the code you posted, but without the bug.
In addition to fixing the bug, I removed all of the unused elements (that is, all the code that did not appear to be used or discussed in your scenario) and refactored the code so that it is more idiomatic of a typical WPF implementation (see helper/base classes at the end). When I run the program, I am able to start and stop the timer objects without any trouble, even after adding new timers to the list.
Notable modifications:
Use of NotifyPropertyChangedBase class as base class for model classes.
Leverage of said base class features for property change notification, by keeping public properties as simple value-storing properties modified as needed.
Use of ICommand implementation for user actions (i.e. "commands").
Separation of timer-specific start/stop functionality when adding timers from view-specific scrolling-into-view behavior.
Remove time-formatting logic from non-UI model object, and put it in the XAML instead
Use conventional (and more readable) - and + operators for DateTime and TimeSpan math
JobTimer.cs:
class JobTimer : NotifyPropertyChangedBase
{
private DateTime _started; // When it was most recently started
private TimeSpan _offset; // The saved value to offset the currently running timer
Timer _swTimer; // The actual tick that updates the screen
private readonly DelegateCommand _startCommand;
private readonly DelegateCommand _stopCommand;
public ICommand StartCommand => _startCommand;
public ICommand StopCommand => _stopCommand;
public JobTimer() : this(TimeSpan.Zero)
{ }
public JobTimer(TimeSpan offset)
{
_offset = offset;
_startCommand = new DelegateCommand(Start, () => !IsRunning);
_stopCommand = new DelegateCommand(Stop, () => IsRunning);
}
private TimeSpan _elapsedTime;
public TimeSpan ElapsedTime
{
get { return _elapsedTime; }
set { _UpdateField(ref _elapsedTime, value); }
}
public void Start()
{
_started = DateTime.UtcNow;
_swTimer = new Timer(TimerChanged, null, 0, 1000);
IsRunning = true;
}
public void Stop()
{
if (_swTimer != null)
{
_swTimer.Dispose();
_swTimer = null;
}
_offset += DateTime.UtcNow - _started;
IsRunning = false;
}
private TimeSpan GetElapsed()
{
return IsRunning ? DateTime.UtcNow - _started + _offset : _offset;
}
// Updates the UI
private void TimerChanged(object state)
{
ElapsedTime = GetElapsed();
}
private bool _isRunning;
public bool IsRunning
{
get { return _isRunning; }
set { _UpdateField(ref _isRunning, value, _OnIsRunningChanged); }
}
private void _OnIsRunningChanged(bool obj)
{
_startCommand.RaiseCanExecuteChanged();
_stopCommand.RaiseCanExecuteChanged();
}
}
MainViewModel.cs:
class MainViewModel : NotifyPropertyChangedBase
{
public ObservableCollection<JobTimer> JobTimers { get; } = new ObservableCollection<JobTimer>();
public ICommand AddTimerCommand { get; }
public MainViewModel()
{
AddTimerCommand = new DelegateCommand(_AddTimer);
_AddTimer();
}
private void _AddTimer()
{
foreach (JobTimer timer in JobTimers)
{
timer.Stop();
}
JobTimer t = new JobTimer();
JobTimers.Add(t);
t.Start();
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainViewModel model = (MainViewModel)DataContext;
model.JobTimers.CollectionChanged += _OnJobTimersCollectionChanged;
}
private void _OnJobTimersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ObservableCollection<JobTimer> jobTimers = (ObservableCollection<JobTimer>)sender;
// Scroll to newly created timer
JobTimer lastTimer = jobTimers.Last();
listBox1.ScrollIntoView(lastTimer);
}
}
MainWindow.xaml:
<Window x:Class="TestSO46416275DateTimeTimer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO46416275DateTimeTimer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:JobTimer}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ElapsedTime, StringFormat=hh\\:mm\\:ss}"/>
<Button Content="Start" Command="{Binding StartCommand}"/>
<Button Content="Stop" Command="{Binding StopCommand}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Add Timer" Command="{Binding AddTimerCommand}" HorizontalAlignment="Left"/>
<ListBox x:Name="listBox1" ItemsSource="{Binding JobTimers}" Grid.Row="1"/>
</Grid>
</Window>
NotifyPropertyChangedBase.cs:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
DelegateCommand.cs:
class DelegateCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public DelegateCommand(Action execute) : this(execute, null)
{ }
public DelegateCommand(Action execute, Func<bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}

Capture user click and typing in a custom WPF TextBox-like control?

Trying to make an equation editor like that in Microsoft Word in C# and WPF. XML cannot be used; it has to be purely programmatic.
Right now I have LineGUIObject : System.Windows.Controls.WrapPanel, which is like System.Windows.Controls.TextBox, except that instead of just showing strings it shows each element of a List<System.Windows.UIElement> in order.
Now I want for a user to be able to click on an instance of LineGUIObject and type into it. The holdup is that I don't know how to capture the user's click or read the input that they type. How can this be done?
Note: This question is not asking how to handle input once captured; just how to get the input in the first place. For example, is there some event that fires off after the user clicks it or something? I can't seem to find one for System.Windows.Controls.WrapPanel, which might imply that I need to use another type of object, or..?
Current code:
public class LineGUIObject
: System.Windows.Controls.WrapPanel
{
private List<System.Windows.UIElement> _uiElementList;
private CursorGUIObject _cursor;
private int? _cursorIndex;
public LineGUIObject(System.Windows.Threading.Dispatcher dispatcher)
: base()
{
this.UIElementList = new List<System.Windows.UIElement>();
this.Cursor = new CursorGUIObject(dispatcher, 25, 1.5, 250);
this.UIElementList.Add(this.Cursor);
this.AddText("[junk string just to see this otherwise invisible object while debugging]");
}
protected void InterpretUserKeyStroke(/* ??? */)
{
//How do we get this method to be called on user input,
//e.g. when the user types "1"?
throw new NotImplementedException();
}
protected void AddText(string text)
{
this.UIElementList.Add(new System.Windows.Controls.TextBlock(new System.Windows.Documents.Run(text)));
this.UpdateDisplay();
}
protected List<System.Windows.UIElement> UIElementList { get { return this._uiElementList; } private set { this._uiElementList = value; } }
protected CursorGUIObject Cursor { get { return this._cursor; } private set { this._cursor = value; } }
protected int? CursorIndex
{
get { return this._cursorIndex; }
set
{
int? nullablePriorIndex = this.CursorIndex;
if (nullablePriorIndex != null)
{
int priorIndex = nullablePriorIndex.Value;
this.UIElementList.RemoveAt(priorIndex);
}
if (value == null)
{
this._cursorIndex = null;
}
else
{
int newIndex = value.Value;
if (newIndex < 0)
{
newIndex = 0;
}
else
{
int thisListCount = this.UIElementList.Count;
if (newIndex > thisListCount) { newIndex = thisListCount; }
}
this.UIElementList.Insert(newIndex, this.Cursor);
this._cursorIndex = newIndex;
}
this.UpdateDisplay();
}
}
protected void UpdateDisplay()
{
this.Children.Clear();
foreach (System.Windows.UIElement uiElement in this.UIElementList) { this.Children.Add(uiElement); }
}
}
public class CursorGUIObject
: System.Windows.Controls.WrapPanel
{
public const double MINIMUM_BLINK_TIME_IN_MS = 5;
public const double MINIMUM_HEIGHT = 0.5;
public const double MINIMUM_WIDTH = 0.5;
private object ToggleVisibilityLock = new object();
private delegate void TimerIntervalDelegate();
private System.Windows.Shapes.Rectangle _rectangle;
private System.Timers.Timer _timer;
private System.Windows.Threading.Dispatcher _dispatcher;
public CursorGUIObject(System.Windows.Threading.Dispatcher dispatcher, double height, double width, double blinkTimeInMS)
{
this.Dispatcher = dispatcher;
System.Windows.Shapes.Rectangle rectangle = new System.Windows.Shapes.Rectangle();
rectangle.Width = width > MINIMUM_WIDTH ? width : MINIMUM_WIDTH;
rectangle.Height = height > MINIMUM_HEIGHT ? height : MINIMUM_HEIGHT;
rectangle.Fill = System.Windows.Media.Brushes.Black;
this.Rectangle = rectangle;
this.Children.Add(rectangle);
System.Timers.Timer timer = new System.Timers.Timer(blinkTimeInMS > MINIMUM_BLINK_TIME_IN_MS ? blinkTimeInMS : MINIMUM_BLINK_TIME_IN_MS);
this.Timer = timer;
timer.Elapsed += timer_Elapsed;
timer.Start();
}
~CursorGUIObject()
{
System.Timers.Timer timer = this.Timer;
if (timer != null) { timer.Dispose(); }
}
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Delegate timerDelegate = new TimerIntervalDelegate(ToggleVisibility);
this.Dispatcher.BeginInvoke(timerDelegate);
}
protected void ToggleVisibility()
{
lock (ToggleVisibilityLock)
{
if (this.Rectangle.Visibility.Equals(System.Windows.Visibility.Hidden))
{
this.Rectangle.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.Rectangle.Visibility = System.Windows.Visibility.Hidden;
}
}
}
protected System.Windows.Shapes.Rectangle Rectangle { get { return this._rectangle; } private set { this._rectangle = value; } }
protected System.Timers.Timer Timer { get { return this._timer; } private set { this._timer = value; } }
protected System.Windows.Threading.Dispatcher Dispatcher { get { return this._dispatcher; } private set { this._dispatcher = value; } }
}
Pretty much all WPF controls provide access to the UIElement.PreviewMouseDown Event, which you can use to monitor mouse clicks. So, this event lets you monitor when each object is clicked on. Next, I'd advise you to use a small Popup control to popup a TextBox that the user could enter a value with:
<Popup Name="Popup">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="5" Padding="5">
<TextBox Text="{Binding InputText}" />
</Border>
</Popup>
Depending on how you have set up your project, you could open the Popup from the event handler:
private void YourObject_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Popup.IsOpen = true;
}
Turns out that LineGUIObject just needed to have this.Focusable = true; set in its constructor so that it could receive the keyboard's focus when clicked.
Now that it can be focused on, this.KeyUp += LineGUIObject_KeyUp; also in the constructor, and
protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
{
this.AddText(e.Key.ToString());
}
Even this had a problem at first since my LineGUIObject was nested in a ScrollViewer which kept stealing focus immediately after the LineGUIObject would receive it. This was fixed by making the ScrollViewer to be unable to get focus, i.e. <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Focusable="False"/>.

WPF MVVM: How to close a window

I have a Button that closes my window when it's clicked:
<Button x:Name="buttonOk" IsCancel="True">Ok</Button>
That's fine until I add a Command to the Button i.e.
<Button x:Name="buttonOk"
Command="{Binding SaveCommand}"
IsCancel="True">Ok</Button>
Now it doesn't close presumably because I am handling the Command. I can fix this by putting an EventHandler in and calling this.Close() i.e.
<Button x:Name="buttonOk"
Click="closeWindow"
Command="{Binding SaveCommand}"
IsCancel="True">Ok</Button>
but now I have code in my code behind i.e. the method SaveCommand. I am using the MVVM pattern and SaveCommand is the only code in my code behind.
How can I do this differently so as not to use code behind?
I just completed a blog post on this very topic. In a nutshell, add an Action property to your ViewModel with get and set accessors. Then define the Action from your View constructor. Finally, invoke your action in the bound command that should close the window.
In the ViewModel:
public Action CloseAction { get; set;}
and in the View constructor:
private View()
{
InitializeComponent();
ViewModel vm = new ViewModel();
this.DataContext = vm;
if ( vm.CloseAction == null )
vm.CloseAction = new Action(this.Close);
}
Finally, in whatever bound command that should close the window, we can simply invoke
CloseAction(); // Calls Close() method of the View
This worked for me, seemed like a fairly elegant solution, and saved me a bunch of coding.
Very clean and MVVM way is to use InteractionTrigger and CallMethodAction defined in Microsoft.Interactivity.Core
You will need to add a new namespace as below
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
You will need the Microsoft.Xmal.Behaviours.Wpf assembly and then the below xaml code will work.
<Button Content="Save" Command="{Binding SaveCommand}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction MethodName="Close"
TargetObject="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType=Window}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
You don't need any code behind or anything else and can also call any other method of Window.
As someone commented, the code I have posted is not MVVM friendly, how about the second solution?
1st, not MVVM solution (I will not delete this as a reference)
XAML:
<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
ViewModel:
public ICommand OkCommand
{
get
{
if (_okCommand == null)
{
_okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
}
return _okCommand ;
}
}
void DoOk(Window win)
{
// Your Code
win.DialogResult = true;
win.Close();
}
bool CanDoOk(Window win) { return true; }
2nd, probably better solution:
Using attached behaviours
XAML
<Button Content="Ok and Close" Command="{Binding OkCommand}" b:CloseOnClickBehaviour.IsEnabled="True" />
View Model
public ICommand OkCommand
{
get { return _okCommand; }
}
Behaviour Class
Something similar to this:
public static class CloseOnClickBehaviour
{
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached(
"IsEnabled",
typeof(bool),
typeof(CloseOnClickBehaviour),
new PropertyMetadata(false, OnIsEnabledPropertyChanged)
);
public static bool GetIsEnabled(DependencyObject obj)
{
var val = obj.GetValue(IsEnabledProperty);
return (bool)val;
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
static void OnIsEnabledPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var button = dpo as Button;
if (button == null)
return;
var oldValue = (bool)args.OldValue;
var newValue = (bool)args.NewValue;
if (!oldValue && newValue)
{
button.Click += OnClick;
}
else if (oldValue && !newValue)
{
button.PreviewMouseLeftButtonDown -= OnClick;
}
}
static void OnClick(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button == null)
return;
var win = Window.GetWindow(button);
if (win == null)
return;
win.Close();
}
}
I'd personally use a behaviour to do this sort of thing:
public class WindowCloseBehaviour : Behavior<Window>
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(WindowCloseBehaviour));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(WindowCloseBehaviour));
public static readonly DependencyProperty CloseButtonProperty =
DependencyProperty.Register(
"CloseButton",
typeof(Button),
typeof(WindowCloseBehaviour),
new FrameworkPropertyMetadata(null, OnButtonChanged));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public Button CloseButton
{
get { return (Button)GetValue(CloseButtonProperty); }
set { SetValue(CloseButtonProperty, value); }
}
private static void OnButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = (Window)((WindowCloseBehaviour)d).AssociatedObject;
((Button) e.NewValue).Click +=
(s, e1) =>
{
var command = ((WindowCloseBehaviour)d).Command;
var commandParameter = ((WindowCloseBehaviour)d).CommandParameter;
if (command != null)
{
command.Execute(commandParameter);
}
window.Close();
};
}
}
You can then attach this to your Window and Button to do the work:
<Window x:Class="WpfApplication6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApplication6"
Title="Window1" Height="300" Width="300">
<i:Interaction.Behaviors>
<local:WindowCloseBehaviour CloseButton="{Binding ElementName=closeButton}"/>
</i:Interaction.Behaviors>
<Grid>
<Button Name="closeButton">Close</Button>
</Grid>
</Window>
I've added Command and CommandParameter here so you can run a command before the Window closes.
For small apps, I use my own Application Controller for showing, closing and disposing windows and DataContexts. It's a central point in UI of an application.
It's something like this:
//It is singleton, I will just post 2 methods and their invocations
public void ShowNewWindow(Window window, object dataContext = null, bool dialog = true)
{
window.DataContext = dataContext;
addToWindowRegistry(dataContext, window);
if (dialog)
window.ShowDialog();
else
window.Show();
}
public void CloseWindow(object dataContextSender)
{
var correspondingWindows = windowRegistry.Where(c => c.DataContext.Equals(dataContextSender)).ToList();
foreach (var pair in correspondingWindows)
{
pair.Window.Close();
}
}
and their invocations from ViewModels:
// Show new Window with DataContext
ApplicationController.Instance.ShowNewWindow(
new ClientCardsWindow(),
new ClientCardsVM(),
false);
// Close Current Window from viewModel
ApplicationController.Instance.CloseWindow(this);
Of course you can find some restrictions in my solution. Again: I use it for small projects, and it's enough. If you're interested, I can post full code here or somewhere else/
I've tried to resolve this issue in some generic, MVVM way, but I always find that I end up unnecessary complex logic. To achieve close behavior I have made an exception from the rule of no code behind and resorted to simply using good ol' events in code behind:
XAML:
<Button Content="Close" Click="OnCloseClicked" />
Code behind:
private void OnCloseClicked(object sender, EventArgs e)
{
Visibility = Visibility.Collapsed;
}
Although I wish this would be better supported using commands/MVVM, I simply think that there is no simpler and more clear solution than using events.
I use the Publish Subscribe pattern for complicated class-dependencies:
ViewModel:
public class ViewModel : ViewModelBase
{
public ViewModel()
{
CloseComand = new DelegateCommand((obj) =>
{
MessageBus.Instance.Publish(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, null);
});
}
}
Window:
public partial class SomeWindow : Window
{
Subscription _subscription = new Subscription();
public SomeWindow()
{
InitializeComponent();
_subscription.Subscribe(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, obj =>
{
this.Close();
});
}
}
You can leverage Bizmonger.Patterns to get the MessageBus.
MessageBus
public class MessageBus
{
#region Singleton
static MessageBus _messageBus = null;
private MessageBus() { }
public static MessageBus Instance
{
get
{
if (_messageBus == null)
{
_messageBus = new MessageBus();
}
return _messageBus;
}
}
#endregion
#region Members
List<Observer> _observers = new List<Observer>();
List<Observer> _oneTimeObservers = new List<Observer>();
List<Observer> _waitingSubscribers = new List<Observer>();
List<Observer> _waitingUnsubscribers = new List<Observer>();
int _publishingCount = 0;
#endregion
public void Subscribe(string message, Action<object> response)
{
Subscribe(message, response, _observers);
}
public void SubscribeFirstPublication(string message, Action<object> response)
{
Subscribe(message, response, _oneTimeObservers);
}
public int Unsubscribe(string message, Action<object> response)
{
var observers = new List<Observer>(_observers.Where(o => o.Respond == response).ToList());
observers.AddRange(_waitingSubscribers.Where(o => o.Respond == response));
observers.AddRange(_oneTimeObservers.Where(o => o.Respond == response));
if (_publishingCount == 0)
{
observers.ForEach(o => _observers.Remove(o));
}
else
{
_waitingUnsubscribers.AddRange(observers);
}
return observers.Count;
}
public int Unsubscribe(string subscription)
{
var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription).ToList());
observers.AddRange(_waitingSubscribers.Where(o => o.Subscription == subscription));
observers.AddRange(_oneTimeObservers.Where(o => o.Subscription == subscription));
if (_publishingCount == 0)
{
observers.ForEach(o => _observers.Remove(o));
}
else
{
_waitingUnsubscribers.AddRange(observers);
}
return observers.Count;
}
public void Publish(string message, object payload)
{
_publishingCount++;
Publish(_observers, message, payload);
Publish(_oneTimeObservers, message, payload);
Publish(_waitingSubscribers, message, payload);
_oneTimeObservers.RemoveAll(o => o.Subscription == message);
_waitingUnsubscribers.Clear();
_publishingCount--;
}
private void Publish(List<Observer> observers, string message, object payload)
{
Debug.Assert(_publishingCount >= 0);
var subscribers = observers.Where(o => o.Subscription.ToLower() == message.ToLower());
foreach (var subscriber in subscribers)
{
subscriber.Respond(payload);
}
}
public IEnumerable<Observer> GetObservers(string subscription)
{
var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription));
return observers;
}
public void Clear()
{
_observers.Clear();
_oneTimeObservers.Clear();
}
#region Helpers
private void Subscribe(string message, Action<object> response, List<Observer> observers)
{
Debug.Assert(_publishingCount >= 0);
var observer = new Observer() { Subscription = message, Respond = response };
if (_publishingCount == 0)
{
observers.Add(observer);
}
else
{
_waitingSubscribers.Add(observer);
}
}
#endregion
}
}
Subscription
public class Subscription
{
#region Members
List<Observer> _observerList = new List<Observer>();
#endregion
public void Unsubscribe(string subscription)
{
var observers = _observerList.Where(o => o.Subscription == subscription);
foreach (var observer in observers)
{
MessageBus.Instance.Unsubscribe(observer.Subscription, observer.Respond);
}
_observerList.Where(o => o.Subscription == subscription).ToList().ForEach(o => _observerList.Remove(o));
}
public void Subscribe(string subscription, Action<object> response)
{
MessageBus.Instance.Subscribe(subscription, response);
_observerList.Add(new Observer() { Subscription = subscription, Respond = response });
}
public void SubscribeFirstPublication(string subscription, Action<object> response)
{
MessageBus.Instance.SubscribeFirstPublication(subscription, response);
}
}
There is a useful behavior for this task which doesn't break MVVM, a Behavior, introduced with Expression Blend 3, to allow the View to hook into commands defined completely within the ViewModel.
This behavior demonstrates a simple technique for allowing the
ViewModel to manage the closing events of the View in a
Model-View-ViewModel application.
This allows you to hook up a behavior in your View (UserControl) which
will provide control over the control's Window, allowing the ViewModel
to control whether the window can be closed via standard ICommands.
Using Behaviors to Allow the ViewModel to Manage View Lifetime in M-V-VM
http://gallery.expression.microsoft.com/WindowCloseBehavior/
Above link has been archived to http://code.msdn.microsoft.com/Window-Close-Attached-fef26a66#content
I struggled with this topic for some time, and eventually went with the simplest approach that is still consistent with MVVM: Have the button execute the Command that does all the heavy lifting and have the button's Click handler close the window.
XAML
<Button x:Name="buttonOk"
Click="closeWindow"
Command="{Binding SaveCommand}" />
XAML.cs
public void closeWindow()
{
this.DialogResult = true;
}
SaveCommand.cs
// I'm in my own file, not the code-behind!
True, there is still code-behind, but there isn't anything inherently bad about that. And it makes the most sense to me, from an OO perspective, to just tell the window to close itself.
We have the name property in the .xaml definition:
x:Name="WindowsForm"
Then we have the button:
<Button Command="{Binding CloseCommand}"
CommandParameter="{Binding ElementName=WindowsForm}" />
Then in the ViewModel:
public DelegateCommand <Object> CloseCommand { get; private set; }
Constructor for that view model:
this.CloseCommand = new DelegateCommand<object>(this.CloseAction);
Then at last, the action method:
private void CloseAction (object obj)
{
Window Win = obj as Window;
Win.Close();
}
I used this code to close a pop-up window from an application..
I found myself having to do this on a WPF application based on .Net Core 3.0, where unfortunately behaviour support was not yet officially available in the Microsoft.Xaml.Behaviors.Wpf NuGet package.
Instead, I went with a solution that made use of the Façade design pattern.
Interface:
public interface IWindowFacade
{
void Close();
}
Window:
public partial class MainWindow : Window, IWindowFacade
…
Standard command property on the view model:
public ICommand ExitCommand
…
Control binding:
<MenuItem Header="E_xit" Command="{Binding ExitCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
Command:
public class ExitCommand : ICommand
{
…
public void Execute(object parameter)
{
var windowFacade = parameter as IWindowFacade;
windowFacade?.Close();
}
…
}
Because the Close() method is already implemented by the Window class, applying the façade interface to the window is the only required code behind in the UI layer (for this simple example). The command in the presentation layer avoids any dependencies on the view/UI layer as it has no idea what it is talking to when it calls the Close method on the façade.
In your current window xaml.cs file, call the below code:
var curWnd = Window.GetWindow(this); // passing current window context
curWnd?.Close();
This should do the thing.
It worked for me, hope will do the same for you )
I have following solution in Silverlight. Would also be in WPF.
ChildWindowExt.cs:
namespace System.Windows.Controls
{
public class ChildWindowExt : ChildWindow
{
public static readonly DependencyProperty IsOpenedProperty =
DependencyProperty.Register(
"IsOpened",
typeof(bool),
typeof(ChildWindowExt),
new PropertyMetadata(false, IsOpenedChanged));
private static void IsOpenedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == false)
{
ChildWindowExt window = d as ChildWindowExt;
window.Close();
}
else if ((bool)e.NewValue == true)
{
ChildWindowExt window = d as ChildWindowExt;
window.Show();
}
}
public bool IsOpened
{
get { return (bool)GetValue(IsOpenedProperty); }
set { SetValue(IsOpenedProperty, value); }
}
protected override void OnClosing(ComponentModel.CancelEventArgs e)
{
this.IsOpened = false;
base.OnClosing(e);
}
protected override void OnOpened()
{
this.IsOpened = true;
base.OnOpened();
}
}
}
ItemWindow.xaml:
<extControls:ChildWindowExt
x:Class="MyProject.ItemWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:extControls="clr-namespace:System.Windows.Controls"
Title="{Binding Title}" IsOpened="{Binding IsOpened, Mode=TwoWay}" Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<Button Command="{Binding UpdateCommand}" Content="OK" Width="70" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</extControls:ChildWindowExt>
ItemViewModel.cs:
private bool _IsOpened;
public bool IsOpened
{
get
{
return _IsOpened;
}
set
{
if (!Equals(_IsOpened, value))
{
_IsOpened = value;
RaisePropertyChanged("IsOpened");
}
}
}
private RelayCommand _UpdateCommand;
/// <summary>
/// Insert / Update data entity
/// </summary>
public RelayCommand UpdateCommand
{
get
{
if (_UpdateCommand == null)
{
_UpdateCommand = new RelayCommand(
() =>
{
// Insert / Update data entity
...
IsOpened = false;
},
() =>
{
return true;
});
}
return _UpdateCommand;
}
}
ItemsViewModel.cs:
private RelayCommand _InsertItemCommand;
/// <summary>
///
/// </summary>
public RelayCommand InsertItemCommand
{
get
{
if (_InsertItemCommand == null)
{
_InsertItemCommand = new RelayCommand(
() =>
{
ItemWindow itemWin = new ItemWindow();
itemWin.DataContext = new ItemViewModel();
itemWin.Show();
// OR
// ItemWindow itemWin = new ItemWindow();
// ItemViewModel newItem = new ItemViewModel();
// itemWin.DataContext = newItem;
// newItem.IsOpened = true;
},
() =>
{
return true;
});
}
return _InsertItemCommand;
}
}
MainPage.xaml:
<Grid x:Name="LayoutRoot">
<Button Command="{Binding InsertItemCommand}" Content="Add New" Width="70" HorizontalAlignment="Left" VerticalAlignment="Center" />
</Grid>
I wish you all good ideas and projects ;-)
This might helps you, closing a wpf window using mvvm with minimal code behind: http://jkshay.com/closing-a-wpf-window-using-mvvm-and-minimal-code-behind/
I think the most simple way has not been included already (almost). Instead of using Behaviours which adds new dependencies just use attached properties:
using System;
using System.Windows;
using System.Windows.Controls;
public class DialogButtonManager
{
public static readonly DependencyProperty IsAcceptButtonProperty = DependencyProperty.RegisterAttached("IsAcceptButton", typeof(bool), typeof(DialogButtonManager), new FrameworkPropertyMetadata(OnIsAcceptButtonPropertyChanged));
public static readonly DependencyProperty IsCancelButtonProperty = DependencyProperty.RegisterAttached("IsCancelButton", typeof(bool), typeof(DialogButtonManager), new FrameworkPropertyMetadata(OnIsCancelButtonPropertyChanged));
public static void SetIsAcceptButton(UIElement element, bool value)
{
element.SetValue(IsAcceptButtonProperty, value);
}
public static bool GetIsAcceptButton(UIElement element)
{
return (bool)element.GetValue(IsAcceptButtonProperty);
}
public static void SetIsCancelButton(UIElement element, bool value)
{
element.SetValue(IsCancelButtonProperty, value);
}
public static bool GetIsCancelButton(UIElement element)
{
return (bool)element.GetValue(IsCancelButtonProperty);
}
private static void OnIsAcceptButtonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Button button = sender as Button;
if (button != null)
{
if ((bool)e.NewValue)
{
SetAcceptButton(button);
}
else
{
ResetAcceptButton(button);
}
}
}
private static void OnIsCancelButtonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Button button = sender as Button;
if (button != null)
{
if ((bool)e.NewValue)
{
SetCancelButton(button);
}
else
{
ResetCancelButton(button);
}
}
}
private static void SetAcceptButton(Button button)
{
Window window = Window.GetWindow(button);
button.Command = new RelayCommand(new Action<object>(ExecuteAccept));
button.CommandParameter = window;
}
private static void ResetAcceptButton(Button button)
{
button.Command = null;
button.CommandParameter = null;
}
private static void ExecuteAccept(object buttonWindow)
{
Window window = (Window)buttonWindow;
window.DialogResult = true;
}
private static void SetCancelButton(Button button)
{
Window window = Window.GetWindow(button);
button.Command = new RelayCommand(new Action<object>(ExecuteCancel));
button.CommandParameter = window;
}
private static void ResetCancelButton(Button button)
{
button.Command = null;
button.CommandParameter = null;
}
private static void ExecuteCancel(object buttonWindow)
{
Window window = (Window)buttonWindow;
window.DialogResult = false;
}
}
Then just set it on your dialog buttons:
<UniformGrid Grid.Row="2" Grid.Column="1" Rows="1" Columns="2" Margin="3" >
<Button Content="Accept" IsDefault="True" Padding="3" Margin="3,0,3,0" DialogButtonManager.IsAcceptButton="True" />
<Button Content="Cancel" IsCancel="True" Padding="3" Margin="3,0,3,0" DialogButtonManager.IsCancelButton="True" />
</UniformGrid>
I also had to deal with this problem, so here my solution. It works great for me.
1. Create class DelegateCommand
public class DelegateCommand<T> : ICommand
{
private Predicate<T> _canExecuteMethod;
private readonly Action<T> _executeMethod;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<T> executeMethod) : this(executeMethod, null)
{
}
public DelegateCommand(Action<T> executeMethod, Predicate<T> canExecuteMethod)
{
this._canExecuteMethod = canExecuteMethod;
this._executeMethod = executeMethod ?? throw new ArgumentNullException(nameof(executeMethod), "Command is not specified.");
}
public void RaiseCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
CanExecuteChanged(this, null);
}
public bool CanExecute(object parameter)
{
return _canExecuteMethod == null || _canExecuteMethod((T)parameter) == true;
}
public void Execute(object parameter)
{
_executeMethod((T)parameter);
}
}
2. Define your command
public DelegateCommand<Window> CloseWindowCommand { get; private set; }
public MyViewModel()//ctor of your viewmodel
{
//do something
CloseWindowCommand = new DelegateCommand<Window>(CloseWindow);
}
public void CloseWindow(Window win) // this method is also in your viewmodel
{
//do something
win?.Close();
}
3. Bind your command in the view
public MyView(Window win) //ctor of your view, window as parameter
{
InitializeComponent();
MyButton.CommandParameter = win;
MyButton.Command = ((MyViewModel)this.DataContext).CloseWindowCommand;
}
4. And now the window
Window win = new Window()
{
Title = "My Window",
Height = 800,
Width = 800,
WindowStartupLocation = WindowStartupLocation.CenterScreen,
};
win.Content = new MyView(win);
win.ShowDialog();
so thats it, you can also bind the command in the xaml file and find the window with FindAncestor and bind it to the command parameter.
I've been searching for a solution to the same problem and found that doing following works fine. The solution is similar to what OP has mentioned in his question with some differences:
No need of IsCancel property.
Code behind should not close window. Just set DialogResult
In my case it first executes code behind and then view model command bound to the button.
XAML
<Button x:Name="buttonOk" Click="Save_Click" Command="{Binding SaveCommand}">OK</Button>
Code Behind
private void Apply_OnClick(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
View Model
private void Save()
{
// Save data.
}
Hope this helps.
You could rephrase the question, and by doing so - coming up with another solution.
How can I enable communication between views, viewmodels and whatnot in an MVVM environment?
You could use the Mediator pattern. It's basically a notification system. For the actual Mediator implementation, google for it or ask me and I can email it.
Make a Command whose purpose is to close the view.
public void Execute( object parameter )
{
this.viewModel.DisposeMyStuff();
Mediator.NotifyColleagues(Mediator.Token.ConfigWindowShouldClose);
}
The Mediator will raise a notification (a token)
Listen to this notification (token) like this in the View codebehind constructor:
public ClientConfigView()
{
InitializeComponent();
Mediator.ListenOn(Mediator.Token.ConfigWindowShouldClose, callback => this.Close() );
}
The solution to close a window in wpf that that worked for me is not answered here so i thought i would add my solution too.
private static Window GetWindow(DependencyObject sender)
{
Window window = null;
if (sender is Window)
window = (Window)sender;
if (window == null)
window = Window.GetWindow(sender);
return window;
}
private void CloseWindow(object sender, RoutedEventArgs e)
{
var button = (Button)sender as DependencyObject;
Window window = GetWindow(button);
if (window != null)
window.Close();
// window.Visibility = Visibility.Hidden;
// choose between window.close or set window.visibility to close or hide the window.
// }
}
Add CloseWindow event to the button in you window as following.
<Button Content="Cancel" Click="CloseWindow" >
Simple approach is close window on saveComand Implementation.
Use below code to close window.
Application.Current.Windows[1].Close();
It will close the child window.
Without any dependencies.
<Window ...>
...
<Button Command="{x:Static SystemCommands.CloseWindowCommand}" />
</Window>
You can do it without code behind. Create command, in Execute method call "Save" method on viewmodel and after that call close method on edit window, which you can pass to the command by parameter:
public void Execute(object parameter)
{
_mainViewModel.SaveSomething();
var editWindow = parameter as MyEditWindow;
editWindow?.Close();
}
Save&Close button XAML:
<Button Content"Save&Close" Command="{Binding SaveCmd}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" IsDefault="True" />

Categories