WPF User Control Parent


183

ฉันมีการควบคุมผู้ใช้ที่ฉันโหลดลงในMainWindowat runtime UserControlฉันไม่สามารถได้รับการจัดการในหน้าต่างที่มีจาก

ฉันได้ลองthis.Parentแล้ว แต่มันก็ว่างเสมอ ไม่มีใครรู้วิธีการจัดการกับหน้าต่างที่มีจากการควบคุมผู้ใช้ใน WPF?

นี่คือวิธีโหลดตัวควบคุม:

private void XMLLogViewer_MenuItem_Click(object sender, RoutedEventArgs e)
{
    MenuItem application = sender as MenuItem;
    string parameter = application.CommandParameter as string;
    string controlName = parameter;
    if (uxPanel.Children.Count == 0)
    {
        System.Runtime.Remoting.ObjectHandle instance = Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName, controlName);
        UserControl control = instance.Unwrap() as UserControl;
        this.LoadControl(control);
    }
}

private void LoadControl(UserControl control)
{
    if (uxPanel.Children.Count > 0)
    {
        foreach (UIElement ctrl in uxPanel.Children)
        {
            if (ctrl.GetType() != control.GetType())
            {
                this.SetControl(control);
            }
        }
    }
    else
    {
        this.SetControl(control);
    }
}

private void SetControl(UserControl control)
{
    control.Width = uxPanel.Width;
    control.Height = uxPanel.Height;
    uxPanel.Children.Add(control);
}

คำตอบ:


346

ลองใช้สิ่งต่อไปนี้:

Window parentWindow = Window.GetWindow(userControlReference);

GetWindowวิธีการจะเดิน VisualTree สำหรับคุณและค้นหาหน้าต่างที่จะเป็นเจ้าภาพการควบคุมของคุณ

คุณควรใช้รหัสนี้หลังจากที่การควบคุมที่มีการโหลด (และไม่ได้อยู่ในตัวสร้างหน้าต่าง) เพื่อป้องกันไม่ให้มีวิธีการกลับมาจากGetWindow nullเช่นวางสายเหตุการณ์:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

6
ยังคงส่งคืนค่าว่าง มันเหมือนกับว่าการควบคุมไม่มีผู้ปกครอง
donniefitz2

2
ฉันใช้รหัสด้านบนและทำให้ parentWindow คืนค่า null ให้ฉันด้วย
Peter Walke

106
ฉันพบว่าสาเหตุที่คืนค่าเป็นโมฆะ ฉันใส่รหัสนี้ลงในตัวสร้างของการควบคุมผู้ใช้ของฉัน คุณควรเรียกใช้รหัสนี้หลังจากโหลดตัวควบคุมแล้ว EG วางสายเหตุการณ์: this.Loaded + = ใหม่ RoutedEventHandler (UserControl_Loaded);
Peter Walke

2
หลังจากตรวจสอบคำตอบจาก Paul แล้วอาจใช้วิธีการ OnInitialized แทน Loaded
Peter Walke

@PeterWalke คุณแก้ปัญหาเวลานานมากของฉัน ... ขอบคุณ
Waqas Shabbir

34

ฉันจะเพิ่มประสบการณ์ของฉัน แม้ว่าการใช้งานเหตุการณ์ที่โหลดสามารถทำงานได้ แต่ฉันคิดว่ามันอาจจะเหมาะสมกว่าที่จะแทนที่วิธี OnInitialized โหลดเกิดขึ้นหลังจากหน้าต่างปรากฏขึ้นเป็นครั้งแรก OnInitialized เปิดโอกาสให้คุณทำการเปลี่ยนแปลงใด ๆ ตัวอย่างเช่นเพิ่มการควบคุมไปยังหน้าต่างก่อนที่จะมีการแสดงผล


8
+1 ถูกต้อง การทำความเข้าใจว่าเทคนิคการใช้งานใดบ้างที่อาจบอบบางในบางครั้งโดยเฉพาะเมื่อคุณมีเหตุการณ์และแทนที่ลงในการผสม (เหตุการณ์ Loaded, การแทนที่ OnLoaded, เหตุการณ์เริ่มต้น, การแทนที่ OnInitialized, etcetcetc) ในกรณีนี้ OnInitialized เหมาะสมเพราะคุณต้องการค้นหาพาเรนต์และต้องเริ่มต้นการควบคุมเพื่อให้พาเรนต์ "มีอยู่" แปลว่าสิ่งที่แตกต่าง
เกร็ก D

3
Window.GetWindowยังคงส่งกลับในnull OnInitializedดูเหมือนว่าจะทำงานในLoadedเหตุการณ์เท่านั้น
Physikbuddha

ต้องกำหนดเหตุการณ์เริ่มต้นก่อน InitializeComponent (); อย่างไรก็ตามองค์ประกอบ Binded (XAML) ของฉันไม่สามารถแก้ไขแหล่งที่มา (หน้าต่าง) ดังนั้นฉันจึงสิ้นสุดการใช้กิจกรรมที่โหลด
Lenor

15

ลองใช้ VisualTreeHelper.GetParent หรือใช้ฟังก์ชัน recursive bellow เพื่อค้นหาหน้าต่างพาเรนต์

 public static Window FindParentWindow(DependencyObject child)
    {
        DependencyObject parent= VisualTreeHelper.GetParent(child);

        //CHeck if this is the end of the tree
        if (parent == null) return null;

        Window parentWindow = parent as Window;
        if (parentWindow != null)
        {
            return parentWindow;
        }
        else
        {
            //use recursion until it reaches a Window
            return FindParentWindow(parent);
        }
    }

ฉันพยายามส่งผ่านโดยใช้รหัสนี้จากภายในการควบคุมผู้ใช้ของฉัน ฉันส่งสิ่งนี้เข้าสู่วิธีการนี้ แต่มันกลับเป็นโมฆะแสดงว่ามันเป็นจุดสิ้นสุดของต้นไม้ (ตามความคิดเห็นของคุณ) คุณรู้ไหมว่าทำไมถึงเป็นเช่นนี้? การควบคุมผู้ใช้มีผู้ปกครองซึ่งเป็นรูปแบบที่มี ฉันจะจัดการกับแบบฟอร์มนี้ได้อย่างไร
Peter Walke

2
ฉันพบว่าสาเหตุที่คืนค่าเป็นโมฆะ ฉันใส่รหัสนี้ลงในตัวสร้างของการควบคุมผู้ใช้ของฉัน คุณควรเรียกใช้รหัสนี้หลังจากโหลดตัวควบคุมแล้ว EG วางสายเหตุการณ์: นี้โหลด + = RoutedEventHandler ใหม่ (UserControl_Loaded)
Peter Walke

ปัญหาอื่นอยู่ในการดีบักเกอร์ VS จะรันโค้ดของเหตุการณ์โหลด แต่จะไม่พบหน้าต่างหลัก
bohdan_trotsenko

1
หากคุณกำลังจะใช้วิธีการของคุณเองคุณควรใช้การรวมกันของ VisualTreeHelper และ LogicalTreeHelper นี่เป็นเพราะตัวควบคุมที่ไม่ใช่หน้าต่างบางตัว (เช่นป๊อปอัป) ไม่มีพ่อแม่ที่มองเห็นและปรากฏว่าตัวควบคุมที่สร้างจากแม่แบบข้อมูลไม่มีพ่อแม่แบบลอจิคัล
Brian Reichle

14

ฉันต้องการใช้วิธี Window.GetWindow (นี่) ภายในตัวจัดการเหตุการณ์ที่โหลด กล่าวอีกนัยหนึ่งฉันใช้ทั้งคำตอบของ Ian Oakes ร่วมกับคำตอบของ Alex เพื่อรับพาเรนต์ของการควบคุมผู้ใช้

public MainView()
{
    InitializeComponent();

    this.Loaded += new RoutedEventHandler(MainView_Loaded);
}

void MainView_Loaded(object sender, RoutedEventArgs e)
{
    Window parentWindow = Window.GetWindow(this);

    ...
}


7

หากคุณกำลังค้นหาคำถามนี้และ VisualTreeHelper ไม่ทำงานสำหรับคุณหรือทำงานเป็นระยะ ๆ คุณอาจต้องรวม LogicalTreeHelper ในอัลกอริทึมของคุณ

นี่คือสิ่งที่ฉันกำลังใช้:

public static T TryFindParent<T>(DependencyObject current) where T : class
{
    DependencyObject parent = VisualTreeHelper.GetParent(current);
    if( parent == null )
        parent = LogicalTreeHelper.GetParent(current);
    if( parent == null )
        return null;

    if( parent is T )
        return parent as T;
    else
        return TryFindParent<T>(parent);
}

คุณพลาดชื่อวิธีการLogicalTreeHelper.GetParentในรหัส
xmedeko

นี่เป็นทางออกที่ดีที่สุดสำหรับฉัน
Jack B Nimble

6

เกี่ยวกับสิ่งนี้:

DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);

public static class ExVisualTreeHelper
{
    /// <summary>
    /// Finds the visual parent.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sender">The sender.</param>
    /// <returns></returns>
    public static T FindVisualParent<T>(DependencyObject sender) where T : DependencyObject
    {
        if (sender == null)
        {
            return (null);
        }
        else if (VisualTreeHelper.GetParent(sender) is T)
        {
            return (VisualTreeHelper.GetParent(sender) as T);
        }
        else
        {
            DependencyObject parent = VisualTreeHelper.GetParent(sender);
            return (FindVisualParent<T>(parent));
        }
    } 
}

5

ฉันพบว่าพาเรนต์ของ UserControl นั้นว่างในตัวสร้างเสมอ แต่ในตัวจัดการเหตุการณ์ใด ๆ ฉันเดาว่ามันต้องมีบางอย่างที่เกี่ยวข้องกับวิธีที่ต้นไม้ควบคุมโหลด ดังนั้นเพื่อให้ได้สิ่งนี้คุณสามารถรับพาเรนต์ในเหตุการณ์ Loaded controls

สำหรับตัวอย่างการเช็คเอาท์คำถามนี้DataContext ของ WPF User Control เป็น Null


1
คุณต้องรอให้มันอยู่ใน "ต้นไม้" ก่อน ค่อนข้างน่ารังเกียจในบางครั้ง
user7116

3

อีกวิธีหนึ่ง:

var main = App.Current.MainWindow as MainWindow;

ทำงานให้ฉันต้องวางไว้ในเหตุการณ์ "โหลด" มากกว่าตัวสร้าง (เปิดหน้าต่างคุณสมบัติคลิกสองครั้งและจะเพิ่มตัวจัดการสำหรับคุณ)
Contango

(การลงคะแนนของฉันสำหรับคำตอบที่ยอมรับโดย Ian นี่เป็นเพียงการบันทึก) สิ่งนี้ไม่ทำงานเมื่อการควบคุมผู้ใช้อยู่ในอีกหน้าต่างหนึ่งด้วย ShowDialog การตั้งค่าเนื้อหาไปยังการควบคุมผู้ใช้ วิธีที่คล้ายกันคือการเดินผ่าน App.Current.Windows และใช้หน้าต่างที่มีเงื่อนไขต่อไปนี้สำหรับ idx จาก (Current.Windows.Count - 1) ถึง 0 (App.Current.Windows [idx] == userControlRef) เป็นจริง . หากเราทำสิ่งนี้ในลำดับกลับกันมันน่าจะเป็นหน้าต่างสุดท้ายและเราได้หน้าต่างที่ถูกต้องด้วยการวนซ้ำเพียงครั้งเดียว userControlRef โดยปกติจะเป็นสิ่งนี้ภายในคลาส UserControl
msanjay

3

มันใช้งานได้สำหรับฉัน:

DependencyObject GetTopLevelControl(DependencyObject control)
{
    DependencyObject tmp = control;
    DependencyObject parent = null;
    while((tmp = VisualTreeHelper.GetParent(tmp)) != null)
    {
        parent = tmp;
    }
    return parent;
}

2

สิ่งนี้ไม่ได้ผลสำหรับฉันเพราะมันไปไกลเกินกว่าต้นไม้และมีหน้าต่างรูตสมบูรณ์สำหรับแอปพลิเคชันทั้งหมด:

Window parentWindow = Window.GetWindow(userControlReference);

อย่างไรก็ตามสิ่งนี้ทำงานเพื่อให้ได้หน้าต่างทันที:

DependencyObject parent = uiElement;
int avoidInfiniteLoop = 0;
while ((parent is Window)==false)
{
    parent = VisualTreeHelper.GetParent(parent);
    avoidInfiniteLoop++;
    if (avoidInfiniteLoop == 1000)
    {
        // Something is wrong - we could not find the parent window.
        break;
    }
}
Window window = parent as Window;
window.DragMove();

คุณควรใช้การตรวจสอบที่ว่างเปล่าแทนการใช้ตัวแปร 'AvoidInfiniteLoop' โดยพลการ เปลี่ยน 'ในขณะที่' ของคุณเพื่อตรวจสอบ null ก่อนและถ้าไม่ใช่ null ให้ตรวจสอบว่าไม่ใช่หน้าต่างหรือไม่ มิฉะนั้นเพียงแค่แบ่ง / ออก
Mark A. Donohoe

@MarquelV ฉันได้ยินคุณ โดยทั่วไปฉันเพิ่มการตรวจสอบ "AvoidInfiniteLoop" ลงในทุก ๆวงที่ในทางทฤษฎีอาจติดอยู่ถ้ามีอะไรผิดพลาด มันเป็นส่วนหนึ่งของการเขียนโปรแกรมการป้องกัน บ่อยครั้งที่มันจ่ายเงินปันผลที่ดีเนื่องจากโปรแกรมหลีกเลี่ยงการแฮงค์ มีประโยชน์มากในระหว่างการแก้ไขข้อบกพร่องและมีประโยชน์อย่างมากในการผลิตหากมีการบันทึกการใช้งานเกิน ฉันใช้เทคนิคนี้ (ท่ามกลางคนอื่น ๆ ) เพื่อเปิดใช้งานการเขียนโค้ดที่มีประสิทธิภาพซึ่งเพิ่งใช้งานได้
Contango

ฉันได้รับการเขียนโปรแกรมป้องกันและฉันเห็นด้วยในหลักการเกี่ยวกับเรื่องนี้ แต่ในฐานะผู้ตรวจสอบรหัสฉันคิดว่าสิ่งนี้จะได้รับการตั้งค่าสถานะเพื่อแนะนำข้อมูลโดยพลการที่ไม่ได้เป็นส่วนหนึ่งของกระแสตรรกะที่แท้จริง คุณมีข้อมูลทั้งหมดที่จำเป็นในการหยุดการเรียกซ้ำแบบไม่สิ้นสุดโดยการตรวจสอบค่าว่างเนื่องจากมันเป็นไปไม่ได้ที่จะเรียกคืนค่าต้นไม้อย่างไม่สิ้นสุด แน่นอนว่าคุณสามารถลืมอัปเดตพาเรนต์และมีการวนซ้ำไม่ จำกัด แต่คุณก็สามารถลืมได้อย่างง่ายดายที่จะอัปเดตตัวแปรตามอำเภอใจนั้น ในคำอื่น ๆ ก็เป็นแล้วเขียนโปรแกรมป้องกันเพื่อตรวจสอบ null โดยไม่ต้องแนะนำของใหม่ข้อมูลที่ไม่เกี่ยวข้อง
Mark A. Donohoe

1
@ MarquelIV ฉันต้องยอมรับ การเพิ่มการตรวจสอบโมฆะเพิ่มเติมเป็นการตั้งโปรแกรมป้องกันที่ดีกว่า
Contango


1
DependencyObject GetTopParent(DependencyObject current)
{
    while (VisualTreeHelper.GetParent(current) != null)
    {
        current = VisualTreeHelper.GetParent(current);
    }
    return current;
}

DependencyObject parent = GetTopParent(thisUserControl);

0

Gold plated edition ด้านบน (ฉันต้องการฟังก์ชั่นทั่วไปซึ่งสามารถอนุมานได้WindowในบริบทของMarkupExtension: -

public sealed class MyExtension : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider) =>
        new MyWrapper(ResolveRootObject(serviceProvider));
    object ResolveRootObject(IServiceProvider serviceProvider) => 
         GetService<IRootObjectProvider>(serviceProvider).RootObject;
}

class MyWrapper
{
    object _rootObject;

    Window OwnerWindow() => WindowFromRootObject(_rootObject);

    static Window WindowFromRootObject(object root) =>
        (root as Window) ?? VisualParent<Window>((DependencyObject)root);
    static T VisualParent<T>(DependencyObject node) where T : class
    {
        if (node == null)
            throw new InvalidOperationException("Could not locate a parent " + typeof(T).Name);
        var target = node as T;
        if (target != null)
            return target;
        return VisualParent<T>(VisualTreeHelper.GetParent(node));
    }
}

MyWrapper.Owner() จะสรุปหน้าต่างอย่างถูกต้องบนพื้นฐานต่อไปนี้:

  • รูทWindowโดยการเดินแผนผังต้นไม้ (ถ้าใช้ในบริบทของ a UserControl)
  • หน้าต่างที่ใช้งานอยู่ (หากใช้ในบริบทของWindowมาร์กอัป)

0

แนวทางและกลยุทธ์ที่ต่างกัน ในกรณีของฉันฉันไม่สามารถหาหน้าต่างโต้ตอบได้โดยใช้ VisualTreeHelper หรือวิธีการขยายจาก Telerik เพื่อค้นหาพาเรนต์ของประเภทที่กำหนด แต่ฉันพบมุมมองโต้ตอบของฉันซึ่งยอมรับการฉีดเนื้อหาที่กำหนดเองโดยใช้ Application.Current.Windows

public Window GetCurrentWindowOfType<TWindowType>(){
 return Application.Current.Windows.OfType<TWindowType>().FirstOrDefault() as Window;
}

0

Window.GetWindow(userControl)จะกลับหน้าต่างที่เกิดขึ้นจริงเท่านั้นหลังจากที่หน้าต่างถูกเริ่มต้น ( InitializeComponent()วิธีการสำเร็จรูป)

ซึ่งหมายความว่าหากการควบคุมผู้ใช้ของคุณเริ่มต้นพร้อมกับหน้าต่าง (ตัวอย่างเช่นคุณใส่การควบคุมผู้ใช้ของคุณลงในไฟล์ xaml ของหน้าต่าง) จากนั้นในOnInitializedเหตุการณ์ของการควบคุมผู้ใช้คุณจะไม่ได้หน้าต่าง (มันจะเป็นโมฆะ) ในกรณีนั้นการควบคุมของผู้ใช้OnInitializedเหตุการณ์จะเริ่มก่อนที่จะเริ่มต้นหน้าต่าง

นี่ก็หมายความว่าหากการควบคุมผู้ใช้ของคุณถูกเริ่มต้นหลังจากหน้าต่างแล้วคุณจะได้รับหน้าต่างอยู่ในตัวสร้างของการควบคุมผู้ใช้


0

หากคุณต้องการรับพาเรนต์เฉพาะไม่เพียง แต่หน้าต่าง, พาเรนต์เฉพาะในโครงสร้างแบบต้นไม้และไม่ใช้การเรียกซ้ำหรือตัวนับลูปวนยากคุณสามารถใช้สิ่งต่อไปนี้:

public static T FindParent<T>(DependencyObject current)
    where T : class 
{
    var dependency = current;

    while((dependency = VisualTreeHelper.GetParent(dependency) ?? LogicalTreeHelper.GetParent(dependency)) != null
        && !(dependency is T)) { }

    return dependency as T;
}

อย่าเพิ่งวางสายนี้ในตัวสร้าง (เนื่องจากParentคุณสมบัติยังไม่เริ่มต้น) เพิ่มในตัวจัดการเหตุการณ์การโหลดหรือในส่วนอื่น ๆ ของแอปพลิเคชันของคุณ

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