WPF ContextMenu does not rely on host visual tree, the Freezable can exist independently, convey dependency property in usercontrol to caller

public class ProxyBinding : Freezable
{
    protected override Freezable CreateInstanceCore()
    {
        return new ProxyBinding();
    }

    public object SourceObject
    {
        get { return (object)GetValue(SourceObjectProperty); }
        set { SetValue(SourceObjectProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SourceObject.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SourceObjectProperty =
        DependencyProperty.Register(
            nameof(SourceObject), 
            typeof(object), 
            typeof(ProxyBinding), 
            new PropertyMetadata(null));
}


<UserControl.Resources>
    <local:ProxyBinding x:Key="ProxyBinding"
                        SourceObject="{Binding RelativeSource={RelativeSource 
        AncestorType=UserControl}}"/>
</UserControl.Resources>

 <ContextMenu>
     <MenuItem Header="{Binding SourceObject.UCFirstMIHeader,
         Source={StaticResource ProxyBinding}}"
               Command="{Binding SourceObject.UCFirstMICmd,
         Source={StaticResource ProxyBinding}}"/>
 </ContextMenu>

 

 

<MenuItem Header="{Binding SourceObject.UCFirstMIHeader,
    Source={StaticResource ProxyBinding}}"
          Command="{Binding SourceObject.UCFirstMICmd,
    Source={StaticResource ProxyBinding}}"/>

 

 

<UserControl x:Class="WpfApp6.UCDG"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp6"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <local:ProxyBinding x:Key="ProxyBinding"
                            SourceObject="{Binding RelativeSource={RelativeSource 
            AncestorType=UserControl}}"/>
    </UserControl.Resources>
    <Grid>
        <DataGrid ItemsSource="{Binding UCDGCollection,
                  RelativeSource={RelativeSource AncestorType=UserControl}}"
                  AutoGenerateColumns="True"
                  VirtualizingPanel.IsVirtualizing="True"
                  VirtualizingPanel.VirtualizationMode="Recycling"
                  VirtualizingPanel.CacheLength="5,5"
                  VirtualizingPanel.CacheLengthUnit="Item"
                  ScrollViewer.IsDeferredScrollingEnabled="True"
                  ScrollViewer.CanContentScroll="True"
                  EnableRowVirtualization="True"
                  EnableColumnVirtualization="True"
                  UseLayoutRounding="True"
                  SnapsToDevicePixels="True" 
                  IsReadOnly="True">
            <DataGrid.Resources>
                <Style TargetType="DataGridRow">
                    <Setter Property="FontSize" Value="30"/>
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="FontSize" Value="50"/>
                            <Setter Property="Foreground" Value="Red"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.Resources>
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="{Binding SourceObject.UCFirstMIHeader,
                        Source={StaticResource ProxyBinding}}"
                              Command="{Binding SourceObject.UCFirstMICmd,
                        Source={StaticResource ProxyBinding}}"/>
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>
    </Grid>
</UserControl>


using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp6
{
    /// <summary>
    /// Interaction logic for UCLbx.xaml
    /// </summary>
    public partial class UCDG : UserControl
    {
        public UCDG()
        {
            InitializeComponent();
        }


        public IList UCDGCollection
        {
            get { return (IList)GetValue(UCDGCollectionProperty); }
            set { SetValue(UCDGCollectionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCDGCollection.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCDGCollectionProperty =
            DependencyProperty.Register(
                nameof(UCDGCollection), 
                typeof(IList), 
                typeof(UCDG), 
                new PropertyMetadata(null));

        public string UCFirstMIHeader
        {
            get { return (string)GetValue(UCFirstMIHeaderProperty); }
            set { SetValue(UCFirstMIHeaderProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCFirstMIHeader.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCFirstMIHeaderProperty =
            DependencyProperty.Register(
                nameof(UCFirstMIHeader), 
                typeof(string), 
                typeof(UCDG), 
                new PropertyMetadata(null));






        public DelCmd UCFirstMICmd
        {
            get { return (DelCmd)GetValue(UCFirstMICmdProperty); }
            set { SetValue(UCFirstMICmdProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCFirstMICmd.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCFirstMICmdProperty =
            DependencyProperty.Register(
                nameof(UCFirstMICmd),
                typeof(DelCmd),
                typeof(UCDG),
                new PropertyMetadata(null));


    }

    

    public class ProxyBinding : Freezable
    {
        protected override Freezable CreateInstanceCore()
        {
            return new ProxyBinding();
        }

        public object SourceObject
        {
            get { return (object)GetValue(SourceObjectProperty); }
            set { SetValue(SourceObjectProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SourceObject.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SourceObjectProperty =
            DependencyProperty.Register(
                nameof(SourceObject), 
                typeof(object), 
                typeof(ProxyBinding), 
                new PropertyMetadata(null));
    }

    public class DelCmd : ICommand
    {
        private Action<object?> execute;
        private Func<object?, bool>? canExecute;
        public DelCmd(Action<object?> executeValue, Func<object?, bool>? canExecuteValue=null)
        {
            execute = executeValue ?? throw new ArgumentNullException(nameof(executeValue));
            canExecute= canExecuteValue;
        }

        public event EventHandler? CanExecuteChanged;

        public bool CanExecute(object? parameter)
        {
            return canExecute == null ? true : canExecute(parameter);
        }

        public void Execute(object? parameter)
        {
            if(!CanExecute(parameter))
            {
                return;
            }
            execute?.Invoke(parameter);            
        }

        public void RaiseCanExecuteChanged()
        {
            var handler = Volatile.Read(ref CanExecuteChanged);
            if (handler == null)
            {
                return;
            }

            if (Application.Current?.Dispatcher?.CheckAccess() == true)
            {
                handler?.Invoke(this, EventArgs.Empty);
            }
            else
            {
                Application.Current?.Dispatcher?.Invoke(() =>
                {
                    handler?.Invoke(this, EventArgs.Empty);
                });
            }
        }
    }
}


<Window x:Class="WpfApp6.MainWindow"
        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:local="clr-namespace:WpfApp6"
        mc:Ignorable="d"
        Title="{Binding MainTitle}"
        WindowState="Maximized">
    <Window.DataContext>
        <local:MainVM/>
    </Window.DataContext>
    <Grid>
        <local:UCDG UCDGCollection="{Binding BooksCollection}"
                    UCFirstMIHeader="{Binding FirstHeader}"
                    UCFirstMICmd="{Binding FirstMICmd}"
                    />
    </Grid>
</Window>


using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Newtonsoft.Json;

namespace WpfApp6
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class MainVM : INotifyPropertyChanged
    {
        string originalUrl = "http://localhost:8080/getbookslist?count=";
        HttpClient client;
        private bool isLoadind = false;
        private object loadingObj = new object();

        public MainVM()
        {
            if(!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
            {
                client = new HttpClient();
                Task.Run(async () =>
                {
                    await LoadDataFromServerRelentless();
                });
            }
        }

        private async Task LoadDataFromServerRelentless()
        {
            while (true)
            {
                await InitBooksCollectionAsync();
                await Task.Delay(20000);
            }
        }

        private async Task InitBooksCollectionAsync(int cnt=1000000)
        {
            lock(loadingObj)
            {
                if(isLoadind)
                {
                    return;
                }
                isLoadind = true;
            }

            try
            {
                await Application.Current?.Dispatcher?.InvokeAsync(() =>
                {
                    MainTitle = $"{DateTime.Now.ToString("yyyyMMddHHmmssffff")},loading...";
                    BooksCollection?.Clear();
                });

                string requestUrl = $"{originalUrl}{cnt}";
                string jsonStr = await client.GetStringAsync(requestUrl).ConfigureAwait(false);
                if (!string.IsNullOrWhiteSpace(jsonStr))
                {
                    List<Book>? bksList = Newtonsoft.Json.JsonConvert.
                        DeserializeObject<List<Book>>(jsonStr);

                    if (bksList != null && bksList.Any())
                    {
                        await Application.Current?.Dispatcher?.InvokeAsync(() =>
                        {
                            BooksCollection = new ObservableCollection<Book>(bksList);
                            MainTitle = $"{DateTime.Now.ToString("yyyyMMddHHmmssffff")}," +
                            $"loaded {BooksCollection?.Count} items," +
                            $"first Id:{BooksCollection[0]?.Id}," +
                            $"Last Id:{BooksCollection[^1]?.Id}";
                        }, System.Windows.Threading.DispatcherPriority.Background);
                    }
                }
            }
            catch (Exception ex)
            {
#if DEBUG
                System.Diagnostics.Debug.WriteLine($"{DateTime.Now},{ex?.Message}");
#else
                System.Diagnostics.Trace.WriteLine($"{DateTime.Now},{ex?.Message}");
#endif
            }
            finally
            {
                isLoadind = false;
            }           
        }

        private ICommand firstMICmd;
        public ICommand FirstMICmd
        {
            get
            {
                if(firstMICmd==null)
                {
                    firstMICmd = new DelCmd(FirstMICmdExecuted);
                }
                return firstMICmd;
            }
        }

        private void FirstMICmdExecuted(object? obj)
        {
             
        }

        private string firstHeader="First Header";
        public string FirstHeader
        {
            get
            {
                return firstHeader;
            }
            set
            {
                if(value!=firstHeader)
                {
                    firstHeader = value;
                    OnPropertyChanged();
                }
            }
        }

        private string mainTitle = $"{DateTime.Now.ToString("yyyyMMddHHmmssffff")}";
        public string MainTitle
        {
            get
            {
                return mainTitle;
            }
            set
            {
                if(value!=mainTitle)
                {
                    mainTitle = value;
                    OnPropertyChanged();
                }
            }
        }

        private ObservableCollection<Book> booksCollection;
        public ObservableCollection<Book> BooksCollection
        {
            get
            {
                return booksCollection;
            }
            set
            {
                if(value!=booksCollection)
                {
                    booksCollection = value;
                    OnPropertyChanged();
                }
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propName="")
        {
            var handler = Volatile.Read(ref PropertyChanged);
            if (handler == null)
            {
                return;
            }
            handler?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
    }

    public class Book
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string ISBN { get; set; }
        public string Author { get; set; }
        public string Comment { get; set; }
        public string Content { get; set; }
        public string Summary { get; set; }
        public string Title { get; set; }
        public string Topic { get; set; }
    }
}

 

 

 

 

 

 

image

 

 

 

 

 

 

 

image

 

 

image

 

 

 

image

 

 

image

 

 

image

 

posted @ 2026-06-14 15:27  FredGrit  阅读(4)  评论(0)    收藏  举报