การเปลี่ยนเคอร์เซอร์ใน WPF บางครั้งก็ใช้ได้ผลบางครั้งก็ไม่ได้ผล


123

ในการควบคุมผู้ใช้ของฉันหลายรายการฉันเปลี่ยนเคอร์เซอร์โดยใช้

this.Cursor = Cursors.Wait;

เมื่อฉันคลิกที่บางสิ่ง

ตอนนี้ฉันต้องการทำสิ่งเดียวกันบนหน้า WPF ด้วยการคลิกปุ่ม เมื่อฉันวางเมาส์เหนือปุ่มเคอร์เซอร์จะเปลี่ยนเป็นรูปมือ แต่เมื่อฉันคลิกเคอร์เซอร์จะไม่เปลี่ยนเป็นเคอร์เซอร์รอ ฉันสงสัยว่าสิ่งนี้เกี่ยวข้องกับความจริงที่ว่ามันเป็นปุ่มหรือเพราะนี่คือหน้าไม่ใช่การควบคุมผู้ใช้? นี่ดูเหมือนพฤติกรรมแปลก ๆ

คำตอบ:


211

คุณต้องการให้เคอร์เซอร์เป็นเคอร์เซอร์ "รอ" เฉพาะเมื่ออยู่เหนือหน้านั้น / usercontrol หรือไม่? ถ้าไม่ฉันขอแนะนำให้ใช้Mouse.OverrideCursor :

Mouse.OverrideCursor = Cursors.Wait;
try
{
    // do stuff
}
finally
{
    Mouse.OverrideCursor = null;
}

สิ่งนี้จะแทนที่เคอร์เซอร์สำหรับแอปพลิเคชันของคุณแทนที่จะเป็นเพียงส่วนหนึ่งของ UI ดังนั้นปัญหาที่คุณอธิบายจะหมดไป


คล้ายกับคำตอบของฉันเองย้อนหลังไป 3 ปี (เกือบเป๊ะ!) ฉันชอบคำตอบในคำถามนี้ แต่คำถามที่ง่ายที่สุดมักจะดึงดูดที่สุด :)
Robin Maben

โซลูชันนี้จะเปลี่ยนเคอร์เซอร์ให้เป็นเคอร์เซอร์ "รอ" แต่จะไม่ปิดใช้งานอินพุตของเมาส์เพิ่มเติม ฉันลองใช้โซลูชันนี้และแม้ว่าเมาส์จะเปลี่ยนเป็นเคอร์เซอร์รอฉันก็ยังสามารถคลิกองค์ประกอบ UI ภายในแอปพลิเคชัน WPF ของฉันได้โดยไม่มีปัญหา มีความคิดอย่างไรที่ฉันจะป้องกันไม่ให้ผู้ใช้ใช้เมาส์ในระหว่างที่เคอร์เซอร์รอทำงานอยู่
Thomas Huber

2
เก่าอย่างที่เป็นอยู่และเป็นที่ยอมรับมันไม่ใช่คำตอบที่เหมาะสม การแทนที่เคอร์เซอร์ของแอปนั้นแตกต่างจากการแทนที่เคอร์เซอร์ควบคุม (และอันที่สองมีปัญหาใน WPF เลย) การลบล้างเคอร์เซอร์ของแอปอาจมีผลข้างเคียงที่น่ารังเกียจตัวอย่างเช่นกล่องข้อความที่โผล่ขึ้นมา (ข้อผิดพลาด) อาจถูกบังคับให้ใช้เคอร์เซอร์ที่ถูกแทนที่แบบเดียวกันอย่างผิดพลาดในขณะที่ตั้งใจจะแทนที่ในขณะที่เมาส์กำลังวางเมาส์เหนือการควบคุมจริงและที่ใช้งานอยู่
Gábor

64

วิธีหนึ่งที่เราทำสิ่งนี้ในแอปพลิเคชันของเราคือการใช้ IDisposable และusing(){}บล็อกเพื่อให้แน่ใจว่าเคอร์เซอร์ถูกรีเซ็ตเมื่อเสร็จสิ้น

public class OverrideCursor : IDisposable
{

  public OverrideCursor(Cursor changeToCursor)
  {
    Mouse.OverrideCursor = changeToCursor;
  }

  #region IDisposable Members

  public void Dispose()
  {
    Mouse.OverrideCursor = null;
  }

  #endregion
}

จากนั้นในรหัสของคุณ:

using (OverrideCursor cursor = new OverrideCursor(Cursors.Wait))
{
  // Do work...
}

การแทนที่จะสิ้นสุดเมื่อ: ถึงจุดสิ้นสุดของคำสั่งการใช้หรือ; หากมีการยกเว้นและการควบคุมออกจากบล็อกคำสั่งก่อนสิ้นสุดคำสั่ง

ปรับปรุง

เพื่อป้องกันไม่ให้เคอร์เซอร์กะพริบคุณสามารถทำได้:

public class OverrideCursor : IDisposable
{
  static Stack<Cursor> s_Stack = new Stack<Cursor>();

  public OverrideCursor(Cursor changeToCursor)
  {
    s_Stack.Push(changeToCursor);

    if (Mouse.OverrideCursor != changeToCursor)
      Mouse.OverrideCursor = changeToCursor;
  }

  public void Dispose()
  {
    s_Stack.Pop();

    Cursor cursor = s_Stack.Count > 0 ? s_Stack.Peek() : null;

    if (cursor != Mouse.OverrideCursor)
      Mouse.OverrideCursor = cursor;
  }

}

2
ทางออกที่ดีกับส่วนที่ใช้ ฉันเขียนเหมือนกันทุกประการในบางโครงการของเรา (โดยไม่มีสแต็กนั่นคือ) สิ่งหนึ่งที่คุณสามารถทำให้การใช้งานง่ายขึ้นคือการเขียน: โดยใช้ (OverrideCursor ใหม่ (Cursors.Wait)) {// do stuff} แทนที่จะกำหนดตัวแปรที่คุณอาจจะไม่ได้ใช้
Olli

1
ไม่ต้องการ. หากคุณตั้งMouse.OverrideCursorไปnullก็ไม่มีการตั้งค่าและไม่นานแทนที่เคอร์เซอร์ระบบ หากฉันแก้ไขเคอร์เซอร์ปัจจุบันโดยตรง (เช่นไม่ได้ลบล้าง) อาจมีปัญหา
Dennis

2
นี่เป็นสิ่งที่ดี แต่จะไม่ปลอดภัยหากหลายมุมมองกำลังอัปเดตเคอร์เซอร์ในเวลาเดียวกัน ง่ายต่อการเข้าสู่สภาวะการแข่งขันโดยที่เคอร์เซอร์ของ ViewA set จากนั้น ViewB จะตั้งค่าอื่นจากนั้น ViewA จะพยายามรีเซ็ตเคอร์เซอร์ (ซึ่งจะทำให้ ViewB ปรากฏขึ้นจากสแต็กและปล่อยให้เคอร์เซอร์ของ ViewA ทำงานอยู่) จนกว่า ViewB จะรีเซ็ตเคอร์เซอร์เพื่อให้สิ่งต่างๆกลับมาเป็นปกติ
Simon Gillbee

2
@SimonGillbee เป็นไปได้แน่นอน - มันไม่ใช่ปัญหาที่ฉันมีเมื่อ 10 ปีที่แล้วเมื่อฉันเขียนสิ่งนี้ หากคุณพบวิธีแก้ปัญหาโดยอาจใช้ a ConcurrentStack<Cursor>อย่าลังเลที่จะแก้ไขคำตอบด้านบนหรือเพิ่มของคุณเอง
Dennis

2
@ เดนนิสฉันเขียนสิ่งนี้เมื่อสองสามวันก่อน (ซึ่งเป็นเหตุผลว่าทำไมฉันถึงมองผ่าน SO) ฉันเล่นกับ ConcurrentStack แต่มันกลายเป็นคอลเลกชันที่ไม่ถูกต้อง Stack อนุญาตให้คุณโผล่จากด้านบนเท่านั้น ในกรณีนี้คุณต้องการลบออกจากตรงกลางของสแต็กหากเคอร์เซอร์นั้นถูกกำจัดก่อนที่ส่วนบนของสแต็กจะถูกกำจัด ฉันลงเอยด้วยการใช้ List <T> กับ ReaderWriterLockSlim เพื่อควบคุมการเข้าถึงพร้อมกัน
Simon Gillbee

38

คุณสามารถใช้ทริกเกอร์ข้อมูล (พร้อมโมเดลมุมมอง) บนปุ่มเพื่อเปิดใช้เคอร์เซอร์รอ

<Button x:Name="NextButton"
        Content="Go"
        Command="{Binding GoCommand }">
    <Button.Style>
         <Style TargetType="{x:Type Button}">
             <Setter Property="Cursor" Value="Arrow"/>
             <Style.Triggers>
                 <DataTrigger Binding="{Binding Path=IsWorking}" Value="True">
                     <Setter Property="Cursor" Value="Wait"/>
                 </DataTrigger>
             </Style.Triggers>
         </Style>
    </Button.Style>
</Button>

นี่คือรหัสจาก view-model:

public class MainViewModel : ViewModelBase
{
   // most code removed for this example

   public MainViewModel()
   {
      GoCommand = new DelegateCommand<object>(OnGoCommand, CanGoCommand);
   }

   // flag used by data binding trigger
   private bool _isWorking = false;
   public bool IsWorking
   {
      get { return _isWorking; }
      set
      {
         _isWorking = value;
         OnPropertyChanged("IsWorking");
      }
   }

   // button click event gets processed here
   public ICommand GoCommand { get; private set; }
   private void OnGoCommand(object obj)
   {
      if ( _selectedCustomer != null )
      {
         // wait cursor ON
         IsWorking = true;
         _ds = OrdersManager.LoadToDataSet(_selectedCustomer.ID);
         OnPropertyChanged("GridData");

         // wait cursor off
         IsWorking = false;
      }
   }
}

4
ฉันไม่ได้รับการโหวตลดลงเช่นกัน คำตอบนี้มีประโยชน์เมื่อคุณใช้ MVvM (ดังนั้นจึงไม่มีโค้ดอยู่ข้างหลัง) และต้องการควบคุมเคอร์เซอร์สำหรับการควบคุมเฉพาะ มีประโยชน์มาก.
Simon Gillbee

4
ฉันใช้ประโยชน์จาก MVVM และนี่คือคำตอบที่สมบูรณ์แบบ
g1ga

ฉันชอบโซลูชันนี้เพราะฉันเชื่อว่ามันจะทำงานได้ดีขึ้นกับ MVVM, viewmodels และอื่น ๆ
Rod

ปัญหาที่ฉันเห็นเมื่อใช้รหัสนี้คือเคอร์เซอร์จะ "รอ" เฉพาะในขณะที่เมาส์วางอยู่เหนือปุ่ม แต่เมื่อคุณเลื่อนเมาส์ออกมันจะกลับเป็น "ลูกศร"
spiderman

7

หากแอปพลิเคชันของคุณใช้งาน async และคุณกำลังเล่นซอกับเคอร์เซอร์ของเมาส์คุณอาจต้องการทำเฉพาะในเธรด UI หลักเท่านั้น คุณสามารถใช้เธรด Dispatcher ของแอพสำหรับสิ่งนั้น:

Application.Current.Dispatcher.Invoke(() =>
{
    // The check is required to prevent cursor flickering
    if (Mouse.OverrideCursor != cursor)
        Mouse.OverrideCursor = cursor;
});

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.