Infinite Loop when binding slider in MVVM - wpf

I have a video progress slider in XAML thus:
<Slider Minimum="0" Value="{Binding Position,Mode=OneWay}" Maximum="{Binding Duration}" IsMoveToPointEnabled="True"/>
And code in my viewmodel to update Position on Clock.CurrentTimeInvalidated(), which keeps the slider tracking current progress:
private void Play()
{
Uri next = _carousel.Dequeue();
_timeline = new MediaTimeline(next);
_timeline.RepeatBehavior = RepeatBehavior.Forever;
_clock = _timeline.CreateClock();
MyMediaElement.Clock = _clock;
_clock.CurrentTimeInvalidated += new EventHandler(UpdatePosition);
_clock.Controller.Begin();
}
public void UpdatePosition(object sender, EventArgs e)
{
Position = MyMediaElement.Position.TotalMilliseconds;
}
This works fine, except when I implement ValueChanged to set Clock.Controller.Seek():
private void seekSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
DisplayViewModel vm = (DisplayViewModel)this.DataContext;
TimeSpan ts = new TimeSpan(0, 0, 0, 0, (int)seekSlider.Value);
vm.MyMediaElement.Clock.Controller.Seek(ts, TimeSeekOrigin.BeginTime);
}
(In my user control's codebehind until I figure out event-to-command routing)
At this point I get an infinite loop. Previously in non-MVVM I simply disabled the Slider.ValueChanged() event handler during UpdatePosition:
public void UpdatePosition(object sender, EventArgs e)
{
seekSlider.ValueChanged -= new EventHandler(seekSlider_ValueChanged);
Position = MyMediaElement.Position.TotalMilliseconds;
seekSlider.ValueChanged += new EventHandler(seekSlider_ValueChanged);
}
...but now I'm in a viewmodel I no longer have access to my slider control.
Is there another way to disable the infinite loop/event handler?

Consider moving the logic from seekSlider_ValueChanged to the setter of the Position property in your ViewModel.

Related

How to execute some code before removing an element from the visual tree?

I am trying to implement some fade-in and fade-out animations for a user control in WPF. For the fade-in animation I was able to use the Loaded event to accomplish that.
public sealed partial class NowPlayingView : UserControl
{
public Duration AnimationDuration
{
get { return (Duration)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
public static readonly DependencyProperty AnimationDurationProperty =
DependencyProperty.Register("AnimationDuration", typeof(Duration), typeof(NowPlayingView), new PropertyMetadata(Duration.Automatic));
public NowPlayingView()
{
Opacity = 0;
InitializeComponent();
Loaded += NowPlayingView_Loaded;
Unloaded += NowPlayingView_Unloaded;
}
private void NowPlayingView_Unloaded(object sender, RoutedEventArgs e)
{
DoubleAnimation animation = new(1.0, 0.0, AnimationDuration);
BeginAnimation(OpacityProperty, animation);
}
private void NowPlayingView_Loaded(object sender, RoutedEventArgs e)
{
DoubleAnimation animation = new (0.0, 1.0, AnimationDuration);
BeginAnimation(OpacityProperty, animation);
}
}
I attempted to use the Unloaded event for the fade-out effect only to find out that the event is fired after the UserControl is removed from the visual tree (when the UserControl is no longer visible or accessible). Is there a way to run some code right before the UserControl "closes", something like the OnClosing event of a Window?
EDIT:
For a bit more context, the UserControl acts as a component of a more complex window. It is activated whenever the Property NowPlayingViewModel is not null and deactivated when null (which I do in order to hide the UserControl). It is when I set the ViewModel to null that I want to run the fade-out animation and I would like to keep the code-behind decoupled from other ViewModel logic.
<!-- Now playing View-->
<ContentControl Grid.RowSpan="3" Grid.ColumnSpan="2" Content="{Binding NowPlayingViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:NowPlayingViewModel}">
<views:NowPlayingView AnimationDuration="00:00:00.8" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
From my testing, I couldn't find any good solution to this so far, though I am open to suggestions that lead to similar behavior.
There is no Closing event in UserControl.. but you can get the parent window when UserControl is loaded and implement the fade-out behavior there..
First, Remove Unloaded += NowPlayingView_Unloaded;
Then, modify the Loaded code a bit..
private Window ParentWindow
{
get
{
DependencyObject parentDepObj = this;
do
{
parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
if (parentDepObj is Window parentWindow) return parentWindow;
} while (parentDepObj != null);
return null;
}
}
private void NowPlayingView_Loaded(object sender, RoutedEventArgs e)
{
DoubleAnimation animation = new(0.0, 1.0, AnimationDuration);
BeginAnimation(OpacityProperty, animation);
var parentWindow = this.ParentOfType<Window>();
parentWindow.Closing += WindowClosing;
}
private void WindowClosing(object sender, CancelEventArgs args)
{
var pw = ParentWindow;
pw.Closing -= WindowClosing;
args.Cancel = true;
var anim = new(1.0, 0.0, AnimationDuration);
anim.Completed += (s, _) => pw.Close();
BeginAnimation(OpacityProperty, anim);
}
Optional Note. You could replace the getter of ParentWindow property with a simple call
private Window ParentWindow => this.ParentOfType<Window>();
Where ParentOfType is an extension function in some public static class Utilities..
public static T ParentOfType<T>(this DependencyObject child) where T : DependencyObject
{
var parentDepObj = child;
do
{
parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
if (parentDepObj is T parent) return parent;
} while (parentDepObj != null);
return null;
}

Wpf Animating Opacity and Drag Moving Window at the Same Time Makes Window Stutter

I'm using this code to lower window's opacity when user moving it and after drag move is completed i'm incresing window's opacity to it's original value.
private void TopBar_Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
var decreaseOpacityAnim = new DoubleAnimation(0.5, (Duration)TimeSpan.FromSeconds(1));
this.BeginAnimation(UIElement.OpacityProperty, decreaseOpacityAnim);
this.DragMove();
var increaseOpacityAnim = new DoubleAnimation(1, (Duration)TimeSpan.FromSeconds(1));
this.BeginAnimation(UIElement.OpacityProperty, increaseOpacityAnim);
}
}
The problem is when user started to move window and the time window's opacity decreasing, window moves stutter. When animation completed window starts moving without stuttering.
Is there anything i can do to fix this?
I handled MouseMove instead of MouseDown. And secondly, you are using both animations while dragging, hence this stutter. Below code works absolutely fine for me.
<Window ...>
<Grid Background="Yellow" MouseMove="Grid_MouseMove_1"/>
</Window>
Code :
private bool animation_is_running = false;
private void Grid_MouseMove_1(object sender, MouseEventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
{
if (!animation_is_running)
{
var decreaseOpacityAnim = new DoubleAnimation(0.5, (Duration)TimeSpan.FromSeconds(1));
this.BeginAnimation(UIElement.OpacityProperty, decreaseOpacityAnim);
animation_is_running = true;
}
this.DragMove();
}
else
{
if (animation_is_running)
{
var increaseOpacityAnim = new DoubleAnimation(1, (Duration)TimeSpan.FromSeconds(1));
this.BeginAnimation(UIElement.OpacityProperty, increaseOpacityAnim);
animation_is_running = false;
}
}
}

Text changed event fired when control loaded, need to prevent on load?

In I have created a control that has a text box and a text changed event handler attached to it - this is in xaml.
The problem: when control is loaded the text changed event is fired, I do not want it to happen when the control is loaded only when I make actually make it change on the control by typing something.
What do you pros suggest I do? :)
All you have to do is check the textbox's IsLoaded property inside the event handler before handling it.
Attach Your EventHandler after the InitializeComponent Method in your constructor not in the Xaml.
i.e.
public MainWindow()
{
InitializeComponent();
textBox1.TextChanged+=new TextChangedEventHandler(textBox1_TextChanged);
}
I noticed that you are talking about an usercontrol, the only thing I can think of off the top of my head is to to create a property that can be used to inhibit the TextChanged Event until the Parent Form finishes loading. See if something like this works.
MainForm Xaml:
<my:UserControl1 setInhibit="True" HorizontalAlignment="Left" Margin="111,103,0,0" x:Name="userControl11" VerticalAlignment="Top" Height="55" Width="149" setText="Hello" />
MainForm CS
private void Window_Loaded(object sender, RoutedEventArgs e)
{
userControl11.setInhibit = false;
}
UserControl:
public UserControl1()
{
InitializeComponent();
textBox1.TextChanged += new TextChangedEventHandler(textBox1_TextChanged);
}
public string setText
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
public bool setInhibit { get; set; }
void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
if (setInhibit) return;
// Do your work here
}
UserControl1.xaml:
<Grid>
<TextBox Text="{Binding MyText, UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBox_TextChanged"/>
</Grid>
where TextChanged is the original event for TextBox
UserControl1.xaml.cs:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
_isFirstTime = true;
DataContext = this;
InitializeComponent();
}
public event TextChangedEventHandler TextBoxTextChanged;
bool _isFirstTime;
//MyText Dependency Property
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(UserControl1), new UIPropertyMetadata(""));
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (TextBoxTextChanged != null)
if (!_isFirstTime)
{
TextBoxTextChanged(sender, e);
}
_isFirstTime = false;
}
}
where TextBox_TextChanged is the customized eventHandler for original TextChanged
and TextBoxTextChanged is more like a wrapper for the original TextChanged
Window.xaml:
<Grid>
<c:UserControl1 TextBoxTextChanged="TextBoxValueChanged"/>
</Grid>
as you see you can add an eventHandler to the event wrapper (TextBoxTextChanged)
Window.xaml.cs:
private void TextBoxValueChanged(object sender, TextChangedEventArgs e)
{
MessageBox.Show("asd");
}
finally TextBoxValueChanged won't be fired the first time Text is changed
private void TextBoxValueChanged(object sender, TextChangedEventArgs e)
{
if (Textbox1.IsFocused)
{
App.Current.Properties["TextChanged"] = "1"; // Set Flag
}
}
private void TextBoxLostFocus(object sender, RoutedEventArgs e)
{
if (App.Current.Properties["TextChanged"] == "1")
{
// Do Your Wor Here
App.Current.Properties["TextChanged"] = "0"; // Clear Flag
}
}
On your XAML:
<TextBox xName="TextBox1" LostFocus="TextBoxLostFocus" TextChanged="TextBoxValueChanged"/>
(This is a very rudimentary, dirty, codebehind hack... checking the IsLoaded property as stated by Brent I found to be efficient)
Here since on textbox control creation it's not focused, the TextChanged event will fire but the flag "1" is NOT set...
Later when user leaves field after editing it, since it had focus the Flag is set... the LostFocus is fired, but only runnig code if textbox was changed.
I found a way of preventing this behavior across multiple inputs without having to create a unique bool for each input...
private void TextChanged_UpdateItem(object sender, TextChangedEventArg e)
{
TextBox txtBox = sender as TextBox;
if (!txtBox.IsFocused)
return;
//The rest of your code here
}
So basically, if the text field doesn't have focus (like on initialization) it just returns. This also prevents it from firing if the data is changed elsewhere. :)
Alternatively, as mentioned by Brent, you can just look for "IsLoaded":
private void TextChanged_UpdateItem(object sender, TextChangedEventArg e)
{
TextBox txtBox = sender as TextBox;
if (!txtBox.IsLoaded)
return;
//The rest of your code here
}

How can I move a WPF Popup when its anchor element moves?

I have a Popup defined like this:
<Popup
Name="myPopup"
StaysOpen="True"
Placement="Bottom"
PlacementRectangle="0,20,0,20"
PlacementTarget="{Binding ElementName=myPopupAnchor}">
<TextBlock ... />
</Popup>
I have added event handlers to the myPopupAnchor element for the events MouseEnter and MouseLeave. The two event handlers toggle the popup's visibility.
My problem is the position of myPopupAnchor is only read when the popup is first shown, or hidden and then shown again. If the anchor moves, the popup does not.
I'm looking for ways to work around this, I want a moving Popup. Can I notify WPF that the PlacementTarget binding has changed and should be read again? Can I manually set the popup's position?
Currently, I have a very crude workaround that involves closing and then opening the popup again, which causes some repainting issues.
I looked at a couple options and samples out there. The thing that seems to work best for me is to "bump" one of the properties that causes the Popup to reposition itself on its own. The property that I used is HorizontalOffset.
I set it to (itself + 1) and then set it back the original value. I do this in an event handler that runs when the window is repositioned.
// Reference to the PlacementTarget.
DependencyObject myPopupPlacementTarget;
// Reference to the popup.
Popup myPopup;
Window w = Window.GetWindow(myPopupPlacementTarget);
if (null != w)
{
w.LocationChanged += delegate(object sender, EventArgs args)
{
var offset = myPopup.HorizontalOffset;
myPopup.HorizontalOffset = offset + 1;
myPopup.HorizontalOffset = offset;
};
}
When the window is moved, the popup will reposition. The subtle change in the HorizontalOffset is not noticed because the window and popup are already moving anyway.
I'm still evaluating whether a popup control is the best option in cases where the control stays open during other interaction. I'm thinking that Ray Burns suggestion to put this stuff in an Adorner layer seems like a good approach for some scenarios.
Just to add on to NathanAW's great solution above, I thought I'd point out some context, such as where to place the C# code in this case. I'm still pretty new to WPF so I struggled at first to figure out where to put NathanAW's code. When I tried putting that code in the constructor for the UserControl that hosted my Popup, Window.GetWindow() always returned Null (so the "bump" code never executed). So I thought that other newbies might benefit from seeing things in context.
Before showing the C# in context, here's some example XAML context to show some relevant elements and their names:
<UserControl x:Class="MyNamespace.View1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<TextBlock x:Name="popupTarget" />
<Popup x:Name="myPopup"
Placement="Bottom"
PlacementTarget="{Binding ElementName=popupTarget}" >
(popup content here)
</Popup>
</UserControl>
Then in the code-behind, to avoid having Window.GetWindow() return Null, wire up a handler to the Loaded event to house NathanAW's code (see Peter Walke's comment on a similar stackoverflow discussion for example). Here's exactly how it all looked in my UserControl code-behind:
public partial class View1 : UserControl
{
// Constructor
public View1()
{
InitializeComponent();
// Window.GetWindow() will return Null if you try to call it here!
// Wire up the Loaded handler instead
this.Loaded += new RoutedEventHandler(View1_Loaded);
}
/// Provides a way to "dock" the Popup control to the Window
/// so that the popup "sticks" to the window while the window is dragged around.
void View1_Loaded(object sender, RoutedEventArgs e)
{
Window w = Window.GetWindow(popupTarget);
// w should not be Null now!
if (null != w)
{
w.LocationChanged += delegate(object sender2, EventArgs args)
{
var offset = myPopup.HorizontalOffset;
// "bump" the offset to cause the popup to reposition itself
// on its own
myPopup.HorizontalOffset = offset + 1;
myPopup.HorizontalOffset = offset;
};
// Also handle the window being resized (so the popup's position stays
// relative to its target element if the target element moves upon
// window resize)
w.SizeChanged += delegate(object sender3, SizeChangedEventArgs e2)
{
var offset = myPopup.HorizontalOffset;
myPopup.HorizontalOffset = offset + 1;
myPopup.HorizontalOffset = offset;
};
}
}
}
private void ppValues_Opened(object sender, EventArgs e)
{
Window win = Window.GetWindow(YourControl);
win.LocationChanged += new EventHandler(win_LocationChanged);
}
void win_LocationChanged(object sender, EventArgs e)
{
if (YourPopup.IsOpen)
{
var mi = typeof(Popup).GetMethod("UpdatePosition", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
mi.Invoke(YourPopup, null);
}
}
If you want to move the popup, there is a simple trick : change its position,then set :
IsOpen = false;
IsOpen = true;
To add to Jason Frank's answer, the Window.GetWindow() approach wouldn't work if the WPF UserControl is ultimately hosted in an WinForms ElementHost. What I needed to find was the ScrollViewer that my UserControl was placed in, as that was the element showing the scrollbars.
This generic recursive method (modified off another answer) will help find the parent of a particular type in the logical tree (it's possible to use the visual tree too), and return it if found.
public static T FindLogicalParentOf<T>(DependencyObject child) where T: FrameworkElement
{
DependencyObject parent = LogicalTreeHelper.GetParent(child);
//Top of the tree
if (parent == null) return null;
T parentWindow = parent as T;
if (parentWindow != null)
{
return parentWindow;
}
//Climb a step up
return FindLogicalParentOf<T>(parent);
}
Call this helper method instead of Window.GetWindow() and continue with Jason's answer of subscribing to the right events. In the case of ScrollViewer, it's the ScrollChanged event instead.
I modified the Code from Jason, because the Popup is already in Foreground if the Window is not Activated. Is there any Option in the Popup class or i is my solution ok?
private void FullLoaded(object sender, RoutedEventArgs e) {
Window CurrentWindow = Window.GetWindow(this.Popup);
if (CurrentWindow != null) {
CurrentWindow.LocationChanged += (object innerSender, EventArgs innerArgs) => {
this.RedrawPopup();
};
CurrentWindow.SizeChanged += (object innerSender, SizeChangedEventArgs innerArgs) => {
this.RedrawPopup();
};
CurrentWindow.Activated += (object innerSender, EventArgs innerArgs) => {
if (this.m_handleDeActivatedEvents && this.m_ShowOnActivated) {
this.Popup.IsOpen = true;
this.m_ShowOnActivated = false;
}
};
CurrentWindow.Deactivated += (object innerSender, EventArgs innerArgs) => {
if (this.m_handleDeActivatedEvents && this.Popup.IsOpen) {
this.Popup.IsOpen = false;
this.m_ShowOnActivated = true;
}
};
}
}
private void RedrawPopup() {
double Offset = this.Popup.HorizontalOffset;
this.Popup.HorizontalOffset = Offset + 1;
this.Popup.HorizontalOffset = Offset;
}
I encapsulated the logic provided by Jason Frank in a class and inherit from the PopUp class.
class MyPopup : Popup
{
private Window _root;
public MyPopup()
{
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
_root = Window.GetWindow(this);
_root.LocationChanged += OnRootLocationChanged;
}
private void OnRootLocationChanged(object sender, EventArgs e)
{
var offset = this.HorizontalOffset;
this.HorizontalOffset = offset + 1;
this.HorizontalOffset = offset;
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
_root.LocationChanged -= OnRootLocationChanged;
Loaded -= OnLoaded;
Unloaded -= OnUnloaded;
}
}
You can not do this. When Popup is displayed on the screen, it does not reposition itself if its parent is repositioned. Thats the behavior of Popup control.
check this: http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.popup.aspx
you can use a Window(with WindowStyle=None) instead of Popup that may solve your problem.
Download the Popup Popup Position Sample at:
http://msdn.microsoft.com/en-us/library/ms771558(v=VS.90).aspx
The code sample uses the class CustomPopupPlacement with a Rect object, and binds to horizontal and vertical offsets to move the Popup.
<Popup Name="popup1" Placement="Bottom" AllowsTransparency="True"
IsOpen="{Binding ElementName=popupOpen, Path=IsChecked}"
HorizontalOffset="{Binding ElementName=HOffset, Path=Value, Mode=TwoWay}"
VerticalOffset="{Binding ElementName=VOffset, Path=Value, Mode=TwoWay}"

How to add a focus style to an editable ComboBox in WPF

I've been looking at the following example on how to style the ComboBox, but I haven't been able to create a focus effect when going into an editable combo box. Whenever the ComboBox receives focus, it should go into edit mode and the component should have a focus style.
The basic problem is that whenever I go into the edit mode, it's not the surrounding ComboBox which actually has the focus, but the text subcomponent and I haven't been able to create a Trigger on the text component which modifies the ComboBox's border style since I don't know how to refer to the parent component from the trigger.
I've tried adding ControlTemplate Trigger on the TextBox, or style trigger. I've tried to refer to the ComboBox by name or by using the TemplateBinding option, but without any luck. A simple example would be very appreciated.
Bind IsKeyboardFocusWithin to IsDropDownOpen
<ComboBox ItemsSource="{Binding SortedItems}"
StaysOpenOnEdit="True"
IsDropDownOpen="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource Self}, Mode=OneWay}" />
private void cmbSpecialHandling_GotFocus(object sender, RoutedEventArgs e)
{
Thickness th = new Thickness(2);
cmbSpecialHandling.BorderThickness = th;
cmbSpecialHandling.BorderBrush = this.FindResource("TabFocusColor") as SolidColorBrush;
}
private void cmbSpecialHandling_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
Thickness th = new Thickness(2);
cmbSpecialHandling.BorderThickness = th;
cmbSpecialHandling.BorderBrush = this.FindResource("TabFocusColor") as SolidColorBrush;
}
private void cmbSpecialHandling_LostFocus(object sender, RoutedEventArgs e)
{
cmbSpecialHandling.BorderBrush = Brushes.Transparent;
}
Set the border brush of combobox in its Gotfocus and make it transparent in lost focus:
private void comboBox_GotFocus(object sender, RoutedEventArgs e)
{
Thickness th = new Thickness(2);
comboBox.BorderThickness = th;
comboBox.BorderBrush = this.FindResource("TabFocusColor") as SolidColorBrush;
or
comboBox.BorderBrush = Brushes.Green;
}
private void comboBox_LostFocus(object sender, RoutedEventArgs e)
{
comboBox.BorderBrush = Brushes.Transparent;
}

Resources