Phụ huynh kiểm soát người dùng WPF


183

Tôi có một điều khiển người dùng mà tôi tải vào MainWindowlúc chạy. Tôi không thể có một tay cầm trên cửa sổ chứa từ UserControl.

Tôi đã thử this.Parent, nhưng nó luôn luôn là null. Có ai biết làm thế nào để có một điều khiển đến cửa sổ chứa từ một điều khiển người dùng trong WPF không?

Đây là cách điều khiển được tải:

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);
}

Câu trả lời:


346

Hãy thử sử dụng như sau:

Window parentWindow = Window.GetWindow(userControlReference);

Các GetWindowphương pháp sẽ đi bộ VisualTree cho bạn và xác định vị trí các cửa sổ đang tổ chức kiểm soát của bạn.

Bạn nên chạy mã này sau khi điều khiển đã được tải (chứ không phải trong hàm tạo của Window) để ngăn GetWindowphương thức quay trở lại null. Ví dụ: kết nối một sự kiện:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

6
Vẫn trả về null. Như thể sự kiểm soát không có cha mẹ.
donniefitz2

2
Tôi đã sử dụng mã ở trên và nhận được ParentWindow cũng trả về null cho tôi.
Peter Walke

106
Tôi tìm ra lý do nó trở về null. Tôi đã đặt mã này vào hàm tạo của điều khiển người dùng của tôi. Bạn nên chạy mã này sau khi điều khiển đã được tải. EG kết nối một sự kiện: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
Peter Walke

2
Sau khi xem xét phản hồi từ Paul, có thể có ý nghĩa khi sử dụng phương thức OnInitialized thay vì Loaded.
Peter Walke

@PeterWalke bạn giải quyết vấn đề rất lâu của tôi ... Cảm ơn
Waqas Shabbir

34

Tôi sẽ thêm kinh nghiệm của tôi. Mặc dù sử dụng sự kiện Loaded có thể thực hiện công việc, tôi nghĩ rằng nó có thể phù hợp hơn để ghi đè phương thức OnInitialized. Tải xảy ra sau khi cửa sổ được hiển thị đầu tiên. OnInitialized cung cấp cho bạn cơ hội để thực hiện bất kỳ thay đổi nào, ví dụ: thêm các điều khiển vào cửa sổ trước khi nó được hiển thị.


8
+1 cho chính xác. Hiểu được kỹ thuật nào có thể được sử dụng đôi khi, đặc biệt là khi bạn có các sự kiện và ghi đè được đưa vào hỗn hợp (Sự kiện được tải, ghi đè OnLoaded, sự kiện được khởi tạo, ghi đè OnInitialized, vvetcetc). Trong trường hợp này, OnInitialized có ý nghĩa bởi vì bạn muốn tìm cha mẹ và điều khiển phải được khởi tạo để cha mẹ "tồn tại". Tải có nghĩa là một cái gì đó khác nhau.
Greg D

3
Window.GetWindowvẫn trả về nulltrong OnInitialized. Có vẻ chỉ để làm việc trong Loadedsự kiện.
Physikbuddha 27/05/2015

Sự kiện được khởi tạo phải được xác định trước khi LaunchizeComponent (); Dù sao, các yếu tố bị ràng buộc (XAML) của tôi không thể giải quyết nguồn (Cửa sổ). Vì vậy, tôi đã kết thúc để sử dụng sự kiện tải.
Lenor

15

Hãy thử sử dụng VisualTreeHelper.GetParent hoặc sử dụng hàm đệ quy dưới đây để tìm cửa sổ cha.

 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);
        }
    }

Tôi đã thử chuyển qua sử dụng mã này từ trong kiểm soát người dùng của tôi. Tôi đã truyền cái này vào phương thức này nhưng nó trả về null, chỉ ra rằng nó là phần cuối của cây (theo nhận xét của bạn). Bạn có biết tại sao không? Kiểm soát người dùng có cha mẹ là biểu mẫu có chứa. Làm thế nào để tôi có một xử lý cho hình thức này?
Peter Walke

2
Tôi tìm ra lý do nó trở về null. Tôi đã đặt mã này vào hàm tạo của điều khiển người dùng của tôi. Bạn nên chạy mã này sau khi điều khiển đã được tải. EG kết nối một sự kiện: this.Loaded + = new RoutedEventHandler (UserControl_Loaded)
Peter Walke

Một vấn đề khác là trong trình gỡ lỗi. VS sẽ thực thi mã của sự kiện Load, nhưng nó sẽ không tìm thấy cha mẹ Window.
bohdan_trotsenko

1
Nếu bạn định thực hiện phương pháp của riêng mình, bạn nên sử dụng kết hợp VisualTreeHelper và LogicalTreeHelper. Điều này là do một số điều khiển ngoài cửa sổ (như Popup) không có cha mẹ trực quan và có vẻ như các điều khiển được tạo từ mẫu dữ liệu không có cha mẹ logic.
Brian Reichle

14

Tôi cần sử dụng phương thức Window.GetWindow (this) trong Trình xử lý sự kiện đã tải. Nói cách khác, tôi đã sử dụng cả câu trả lời của Ian Oakes kết hợp với câu trả lời của Alex để có được cha mẹ của người dùng kiểm soát.

public MainView()
{
    InitializeComponent();

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

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

    ...
}

7

Cách tiếp cận này hiệu quả với tôi nhưng nó không cụ thể như câu hỏi của bạn:

App.Current.MainWindow

7

Nếu bạn đang tìm thấy câu hỏi này và VisualTreeHelper không hoạt động cho bạn hoặc hoạt động không thường xuyên, bạn có thể cần đưa LogicalTreeHelper vào thuật toán của mình.

Đây là những gì tôi đang sử dụng:

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);
}

Bạn bỏ lỡ một tên phương thức LogicalTreeHelper.GetParenttrong mã.
xmedeko

Đây là giải pháp tốt nhất cho tôi.
Jack B Nimble

6

Còn cái này thì sao:

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

Tôi đã thấy rằng cha mẹ của UserControl luôn là null trong hàm tạo, nhưng trong bất kỳ trình xử lý sự kiện nào, cha mẹ được đặt chính xác. Tôi đoán nó phải có cái gì đó để làm với cách cây điều khiển được tải. Vì vậy, để giải quyết vấn đề này, bạn chỉ cần lấy cha mẹ trong sự kiện Loaded điều khiển.

Đối với một ví dụ kiểm tra câu hỏi này , DataContext của WPF User Control là Null


1
Bạn phải đợi nó ở trong "cái cây" trước. Khá đáng ghét đôi khi.
dùng7116

3

Cách khác:

var main = App.Current.MainWindow as MainWindow;

Làm việc cho tôi, phải đặt nó trong sự kiện "Đã tải" chứ không phải là hàm tạo (hiển thị cửa sổ thuộc tính, nhấp đúp chuột và nó sẽ thêm trình xử lý cho bạn).
Contango

(Phiếu bầu của tôi là cho câu trả lời được chấp nhận bởi Ian, đây chỉ là bản ghi) Điều này không hoạt động khi điều khiển người dùng ở trong một cửa sổ khác với ShowDialog, đặt nội dung thành điều khiển người dùng. Một cách tiếp cận tương tự là đi qua App.Cản.Windows và sử dụng cửa sổ trong đó điều kiện sau, cho idx từ (Current.Windows.Count - 1) đến 0 (App.Cản.Windows [idx] == userControlRef) là đúng . Nếu chúng ta làm điều này theo thứ tự ngược lại, nó có khả năng là cửa sổ cuối cùng và chúng ta có được cửa sổ chính xác chỉ với một lần lặp. userControlRef thường này trong lớp UserControl.
msanjay 17/2/2016

3

Nó làm việc cho tôi:

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

3

Điều này không làm việc cho tôi, vì nó đã đi quá xa cây và có cửa sổ gốc tuyệt đối cho toàn bộ ứng dụng:

Window parentWindow = Window.GetWindow(userControlReference);

Tuy nhiên, điều này đã làm việc để có được cửa sổ ngay lập tức:

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();

Bạn nên sử dụng kiểm tra null thay vì biến tùy ý 'TránhInfiniteLoop'. Thay đổi 'while' của bạn để kiểm tra null trước và nếu không null, sau đó kiểm tra xem đó có phải là cửa sổ không. Nếu không, chỉ cần phá vỡ / thoát.
Đánh dấu A. Donohoe

@MarquelV Tôi nghe bạn. Nói chung, tôi thêm một kiểm tra "TránhInfiniteLoop" vào mọi vòng lặp trong lý thuyết có thể bị kẹt nếu có sự cố. Đó là một phần của lập trình phòng thủ. Mỗi lần như vậy, nó trả cổ tức tốt vì chương trình tránh bị treo. Rất hữu ích trong quá trình gỡ lỗi và rất hữu ích trong sản xuất nếu ghi đè vượt mức. Tôi sử dụng kỹ thuật này (trong số nhiều người khác) để cho phép viết mã mạnh mẽ chỉ hoạt động.
Contango

Tôi nhận được lập trình phòng thủ và tôi đồng ý về nguyên tắc về nó, nhưng với tư cách là người đánh giá mã, tôi nghĩ rằng điều này sẽ bị gắn cờ vì giới thiệu dữ liệu tùy ý không phải là một phần của luồng logic thực tế. Bạn đã có tất cả thông tin cần thiết để ngăn chặn đệ quy vô hạn bằng cách kiểm tra null vì không thể lấy lại một cây vô hạn. Chắc chắn bạn có thể quên cập nhật cha mẹ và có một vòng lặp vô hạn, nhưng bạn có thể dễ dàng quên cập nhật biến tùy ý đó. Nói cách khác, nó đã được lập trình phòng thủ để kiểm tra null mà không cần giới thiệu dữ liệu mới, không liên quan.
Đánh dấu A. Donohoe

1
@MarquelIV Tôi phải đồng ý. Thêm một kiểm tra null bổ sung là lập trình phòng thủ tốt hơn.
Contango


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

DependencyObject parent = GetTopParent(thisUserControl);

0

Phiên bản mạ vàng ở trên (Tôi cần một chức năng chung có thể suy ra Windowtrong bối cảnh của 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() sẽ suy ra chính xác một Cửa sổ trên cơ sở sau:

  • gốc Windowbằng cách đi bộ cây trực quan (nếu được sử dụng trong ngữ cảnh của a UserControl)
  • cửa sổ trong đó nó được sử dụng (nếu nó được sử dụng trong bối cảnh Windowđánh dấu)

0

Cách tiếp cận khác nhau và chiến lược khác nhau. Trong trường hợp của tôi, tôi không thể tìm thấy cửa sổ hộp thoại của mình thông qua việc sử dụng VisualTreeHelper hoặc các phương thức mở rộng từ Telerik để tìm cha mẹ của loại đã cho. Thay vào đó, tôi tìm thấy chế độ xem hộp thoại của mình chấp nhận nội dung tùy chỉnh bằng cách sử dụng Application.Cản.Windows.

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

0

Các Window.GetWindow(userControl)sẽ trở lại cửa sổ thực tế chỉ sau khi cửa sổ được khởi tạo ( InitializeComponent()phương pháp hoàn thành).

Điều này có nghĩa là, nếu điều khiển người dùng của bạn được khởi tạo cùng với cửa sổ của nó (ví dụ: bạn đặt điều khiển người dùng của mình vào tệp xaml của cửa sổ), thì trong OnInitializedsự kiện của điều khiển người dùng, bạn sẽ không nhận được cửa sổ (nó sẽ là null), vì trường hợp đó là sự kiểm soát của người dùngOnInitialized sự kiện kích hoạt trước khi cửa sổ được khởi tạo.

Điều này cũng có nghĩa là nếu điều khiển người dùng của bạn được khởi tạo sau cửa sổ của nó, thì bạn có thể lấy cửa sổ đã có trong hàm tạo của điều khiển người dùng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.