简体   繁体   中英

WPF UI hangs on event fire

I am trying to programmatically scroll a ScrollViewer. But my UI hangs as soon as the event fires. If I take out while loop, this problem is gone, but then I do not get the behavior I want.

I am using WPF 4.0

Here is the code:

<Page
    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:System="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d"
    x:Class="MyApp.Home"
    x:Name="HomePage"
    WindowTitle="Home"
    FlowDirection="LeftToRight" Foreground="{x:Null}" MinWidth="1100" MinHeight="629" Loaded="HomePage_Loaded" SizeChanged="HomePage_SizeChanged">

    <Grid x:Name="LayoutRoot">
        <TextBlock HorizontalAlignment="Right" Height="48.806" Margin="0,15,15,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="138.908" FontSize="34.667" FontFamily="/MyApp;component/Fonts/#Calibri" Text="featured." Foreground="#FF333333" d:IsLocked="True"/>
        <ScrollViewer Height="215" Margin="-20.648,67.806,-20.648,0" VerticalAlignment="Top" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Disabled" Name="FeaturedScroll" Width="1141.296">
            <ListView x:Name="FeaturedList" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="{x:Null}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Disabled">
                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
            </ListView>
        </ScrollViewer>
        <Rectangle x:Name="Left" Height="215" Width="77.129" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,68,0,0" MouseEnter="Left_MouseEnter" Cursor="Hand" Fill="#00000000" MouseLeave="Left_MouseLeave" />
        <Rectangle x:Name="Right" Height="215" Width="77.129" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,68,0,0" MouseEnter="Right_MouseEnter" Cursor="Hand" Fill="#00000000" MouseLeave="Right_MouseLeave" />
    </Grid>
</Page>

private void Left_MouseEnter(object sender, MouseEventArgs e) {
    ScrollLeft = true;

    while (ScrollLeft == true)
        FeaturedScroll.ScrollToHorizontalOffset(FeaturedScroll.HorizontalOffset - 1);
}

private void Right_MouseEnter(object sender, MouseEventArgs e) {
    ScrollRight = true;

    while (ScrollRight == true)
        FeaturedScroll.ScrollToHorizontalOffset(FeaturedScroll.HorizontalOffset + 1);
}

private void Left_MouseLeave(object sender, MouseEventArgs e) {
    ScrollLeft = false;
}

private void Right_MouseLeave(object sender, MouseEventArgs e) {
    ScrollRight = false;
}

Your loop will loop indefinitely,as there's nothing to make ScrollLeft or ScrollRight false. Since the loop doesnt exit, the event handler never finishes, and no new events will be fired.

I'd suggest maybe using a storyboard animation to scroll and then investigating the state of the mouse when the animation has concluded.

Your code seems to expect that events (Left_MouseLeave) will fire while another event (Left_MouseEnter) still is executing.

That is not the case, events are serviced by 1 thread and will not overlap.

You will have to change to an algorithm that is more event-driven. I don't see a MouseHover in WPF so you will need something else.

You can't scroll from another thread, but you don't have to because your can fix the problem with some simple code changes.

As everyone else said you can't get mouse leave event while the mouse enter event is working, you have to change your code so that the mouse enter event returns immediately (by the way, all events should return immediately, otherwise you program - and in extreme cases the entire system will become non-responsive).

Here are two options you can try:

  1. Add a DispatcherTimer to your class, scroll a fixed amount in the timer's Tick event, on mouse enter call the timer Start method, on mouse leave call Stop.

  2. Use animations, start scrolling using a DoubleAnimation on mouse enter, stop the animation and check current value on mouse leave

  3. Use RepeatButton, if you scroll a fixed amount on the RepeatButton Click event the event will be called repeatedly while the button is pressed (this changes the behavior a little bit because you have to press the button not just hover over it).

There are probably a lot of other options as well, and non of them involve threading (because multi-threading solution won't work here)

Here is the final code. Hope it helps someone :)

public MySlider() {
    this.InitializeComponent();

    ScrollLeft = true;

    timer = new DispatcherTimer();
    timer.Interval = TimeSpan.FromMilliseconds(0.01);
    timer.Tick += new EventHandler(timer_Tick);
}

void timer_Tick(object sender, EventArgs e) {
    if (ScrollLeft) MyScrollViewer.ScrollToHorizontalOffset(MyScrollViewer.HorizontalOffset - 0.1);
    else MyScrollViewer.ScrollToHorizontalOffset(MyScrollViewer.HorizontalOffset + 0.1);
}

private void Left_MouseEnter(object sender, MouseEventArgs e) {
    ScrollLeft = true;

    timer.Start();
}

private void Right_MouseEnter(object sender, MouseEventArgs e) {
    ScrollLeft = false;

    timer.Start();
}

private void Left_MouseLeave(object sender, MouseEventArgs e) {
    timer.Stop();

    ScrollLeft = false;
}

private void Right_MouseLeave(object sender, MouseEventArgs e) {
    timer.Stop();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM