如何为自定义控件创建事件

Posted

技术标签:

【中文标题】如何为自定义控件创建事件【英文标题】:How to create an event for custom control 【发布时间】:2016-08-11 09:26:23 【问题描述】:

我创建了一个带有图像、2 个标签和一个按钮的自定义控件(列表)。我想调用自定义控件的按钮单击。我的整个控件也将有一个单独的事件。

这是我的用户控件模板代码:

namespace __

    public class SelectMultipleBasePage<T> : ContentPage
    

        public class WrappedSelection<T> : INotifyPropertyChanged
        
            public T Item  get; set; 
            bool isSelected = false;
            public bool IsSelected
            
                get
                
                    return isSelected;
                
                set
                
                    if (isSelected != value)
                    
                        isSelected = value;
                        PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
                        //                      PropertyChanged (this, new PropertyChangedEventArgs (nameof (IsSelected))); // C# 6
                    
                
            
            public event PropertyChangedEventHandler PropertyChanged = delegate  ;
        
        public class BackGroundColorConverter : IValueConverter
        
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            
                if (value is bool)
                
                    if ((bool)value)
                    
                        return Color.FromHex("#DEE4EA");
                    
                    else
                    
                        return Color.White;
                    
                
                else
                
                    return Color.White;
                
            

            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            
                throw new NotImplementedException();
            

        

        public class WrappedItemSelectionTemplate : ViewCell
        
             static int i=0; // don't forget to make it static 
            public WrappedItemSelectionTemplate()
                : base()
             

                 var items = RecordingListPage.items;
                 Grid objGrid = new Grid();
                 objGrid.RowDefinitions.Add(new RowDefinition
                 
                     Height = new GridLength(1, GridUnitType.Star)
                 );
                 objGrid.ColumnDefinitions.Add(new ColumnDefinition
                 
                     Width = new GridLength(75, GridUnitType.Absolute),
                 );
                 objGrid.ColumnDefinitions.Add(new ColumnDefinition
                 
                     Width = new GridLength(1, GridUnitType.Star)
                 );
                 objGrid.ColumnDefinitions.Add(new ColumnDefinition
                 
                     Width = new GridLength(75, GridUnitType.Absolute),
                 );

                 // Column 1:-
                 Image objImage = new Image();
                 objImage.SetBinding(Image.SourceProperty, new Binding("Item.Image"));
                 objGrid.Children.Add(objImage, 0, 0);
                 // Column 2:-
                 StackLayout objStackLayoutCol2 = new StackLayout();
                 objGrid.Children.Add(objStackLayoutCol2, 1, 0);

                 Label name = new Label()
                 
                     Text = "Name",
                     Style = (Style)Application.Current.Resources["LabelStyle"],
                 ;
                 Label date = new Label()
                 
                     Text = "Date",
                     Style = (Style)Application.Current.Resources["LabelStyleTiny"]
                 ;
                 name.SetBinding(Label.TextProperty, new Binding("Item.Name"));
                 date.SetBinding(Label.TextProperty, new Binding("Item.Date"));
                 objStackLayoutCol2.Children.Add(name);
                 objStackLayoutCol2.Children.Add(date);
                 objStackLayoutCol2.Padding = new Thickness(10);

                 Label objImageView = new Label();
                 objImageView.Text = FontAwesome.FAPencilSquareO;
                 objImageView.FontSize = 35;
                 objImageView.VerticalOptions = LayoutOptions.CenterAndExpand;
                 objImageView.HorizontalOptions = LayoutOptions.CenterAndExpand;
                 objImageView.TextColor = Color.Black;

                 StackLayout stv = new StackLayout();
                 stv.Children.Add(objImageView);
                 stv.Padding = new Thickness(10);
                 stv.HorizontalOptions = LayoutOptions.Center;
                 stv.VerticalOptions = LayoutOptions.Center;

                 objImageView.StyleId = items[i].Id.ToString();
                 i++;
                 if (i == items.Count)
                 
                     i = 0;
                 

                 var tapGestureRecognizer = new TapGestureRecognizer();
                  tapGestureRecognizer.Tapped += OnImageBtnTapped;
                 objImageView.GestureRecognizers.Add(tapGestureRecognizer);

                 objGrid.Children.Add(stv, 2, 0);

                 var moreAction = new MenuItem  Text = "More" ;

                 moreAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
                 moreAction.Clicked += (sender, e) =>
                 
                     var mi = ((MenuItem)sender);
                     //Debug.WriteLine("More Context Action clicked: " + mi.CommandParameter);
                 ;

                 var deleteAction = new MenuItem  Text = "Delete", IsDestructive = true ; // red background
                 deleteAction.Icon = Device.OnPlatform("Icons/cancel.png", "cancel.png", "Images/cancel.png");
                 deleteAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
                 deleteAction.Clicked += (sender, e) =>
                 
                     var mi = ((MenuItem)sender);
                     //Debug.WriteLine("Delete Context Action clicked: " + mi.CommandParameter);
                 ;

                 //
                 // add context actions to the cell
                 //
                 ContextActions.Add(moreAction);
                 ContextActions.Add(deleteAction);
                 //objGrid.Padding = new Thickness(10);

                 StackLayout st = new StackLayout();
                 st.Children.Add(objGrid);
                 st.Children.Add(new BoxView()  Color = Color.FromHex("#A4B3C1"), WidthRequest = 100, HeightRequest = 1 );


                 View = st;


             

            private void OnImageBtnTapped(object sender, EventArgs e)
            
                var tappedImage = (Label)sender;
                var ImageId = Convert.ToInt32(tappedImage.StyleId);
                Application.Current.Properties["ItemId"] = ImageId;
                MessagingCenter.Send(new RedirectClass.OpenRecordingDetails(), RedirectClass.OpenRecordingDetails.Key);
            

        
        public static List<WrappedSelection<T>> WrappedItems = new List<WrappedSelection<T>>();
        public SelectMultipleBasePage(List<T> items)
        
            WrappedItems = items.Select(item => new WrappedSelection<T>()  Item = item, IsSelected = false ).ToList();
            ListView mainList = new ListView()
            
                ItemsSource = WrappedItems,
                ItemTemplate = new DataTemplate(typeof(WrappedItemSelectionTemplate)),
            ;

            mainList.ItemSelected += (sender, e) =>
            
                if (e.SelectedItem == null) return;
                var o = (WrappedSelection<T>)e.SelectedItem;
                o.IsSelected = !o.IsSelected;
                ((ListView)sender).SelectedItem = null; //de-select
            ;
            Content = mainList;
            mainList.HasUnevenRows = true;


            if (Device.OS == TargetPlatform.WinPhone)
               // fix issue where rows are badly sized (as tall as the screen) on WinPhone8.1
                mainList.RowHeight = 40;
                // also need icons for Windows app bar (other platforms can just use text)
                ToolbarItems.Add(new ToolbarItem("All", "check.png", SelectAll, ToolbarItemOrder.Primary));
                ToolbarItems.Add(new ToolbarItem("None", "cancel.png", SelectNone, ToolbarItemOrder.Primary));
            
            else
            
                ToolbarItems.Add(new ToolbarItem("All", null, SelectAll, ToolbarItemOrder.Primary));
                ToolbarItems.Add(new ToolbarItem("None", null, SelectNone, ToolbarItemOrder.Primary));
            
        
        void SelectAll()
        
            foreach (var wi in WrappedItems)
            
                wi.IsSelected = true;
            
        
        void SelectNone()
        
            foreach (var wi in WrappedItems)
            
                wi.IsSelected = false;
            
        
        public static List<T> GetSelection()
        
            return WrappedItems.Where(item => item.IsSelected).Select(wrappedItem => wrappedItem.Item).ToList();
        
    

我有一个事件 OnImageBtnTapped 我想从我的内容页面访问它。我该怎么做?

编辑

感谢@Jason,我设法在使用控件的代码中获得了事件点击功能。

在自定义控件类中:

private void OnImageBtnTapped(object sender, EventArgs e)

   if (OnImageSelected != null) 
   
        OnImageSelected(sender,e);
   

在使用控件的代码中,

SelectMultipleBasePage<ListItems>.OnImageSelected += ListPage_OnImageSelected;

void ListPage_OnImageSelected(object sender, EventArgs e)


    var tappedImage = (Label)sender;
    var ImageId = Convert.ToInt32(tappedImage.StyleId);

    Application.Current.Properties["ItemId"] = ImageId;

    MessagingCenter.Send(new RedirectClass.OpenRecordingDetails(), RedirectClass.OpenRecordingDetails.Key);

【问题讨论】:

【参考方案1】:

这与为任何 C# 代码创建自定义事件没有什么不同

首先,在你的类中创建一个公共事件和处理程序:

// note: you may want to create your own ImageEventArgs class that inherits from EventArgs
public delegate void ImageSelectedHandler(object sender, EventArgs e);
public event ImageSelectedHandler OnImageSelected;

接下来,在控件内部你可以调用处理程序(如果存在)

private void OnImageBtnTapped(object sender, EventArgs e)

  var tappedImage = (Label)sender;
  var ImageId = Convert.ToInt32(tappedImage.StyleId);
  Application.Current.Properties["ItemId"] = ImageId;

  MessagingCenter.Send(new RedirectClass.OpenRecordingDetails(), RedirectClass.OpenRecordingDetails.Key);

  // check if a handler is assigned
  if (OnImageSelected != null) 
    OnImageSelected(this,new EventArgs(...));
  

最后,在使用控件的代码中,像往常一样定义一个事件处理程序

myControl.OnImageSelected += delegate 
  // handler logic goes here
;

【讨论】:

它确实有效,但不知何故我无法定义这样的偶数句柄:SelectMultipleBasePage&lt;ListItems&gt; multiPage =new SelectMultipleBasePage&lt;ListItems&gt;(items); multiPage.OnImageSelected += RecordingListPage_OnImageSelected; 我想在不同的页面上使用这个控件和事件。我该怎么做?【参考方案2】:

您可以将处理程序从父级移交给子级。然后它不涉及静态事件处理程序。您只需向您的WrappedItemSelectionTemplate 添加一个参数并使用new DataTemplate(() =&gt; new WrappedItemSelectionTemplate(HandleImageSelected)) 创建您的模板。

public class SelectMultipleBasePage<T> : ContentPage

    public delegate void ImageSelectedHandler(object sender, EventArgs e);
    public event ImageSelectedHandler OnImageSelected;

    public class WrappedItemSelectionTemplate : ViewCell
    
        private readonly ImageSelectedHandler _parentHandler;
        public WrappedItemSelectionTemplate(ImageSelectedHandler parentHandler)
            : base()
        
            _parentHandler = parentHandler;

            // ...
            View = st;
        

        private void OnImageBtnTapped(object sender, EventArgs e)
        
            //...
            _parentHandler.Invoke(sender, e);
        

    
    public static List<WrappedSelection<T>> WrappedItems = new List<WrappedSelection<T>>();
    public SelectMultipleBasePage(List<T> items)
    
        WrappedItems = items.Select(item => new WrappedSelection<T>()  Item = item, IsSelected = false ).ToList();
        ListView mainList = new ListView()
        
            ItemsSource = WrappedItems,
            ItemTemplate = new DataTemplate(() => new WrappedItemSelectionTemplate(HandleImageSelected)),
        ;
        // ...
    

    private void HandleImageSelected(object sender, EventArgs e)
    
        if (OnImageSelected != null)
        
            OnImageSelected(sender, e);
        
    

【讨论】:

如何访问正在使用该控件的页面中的事件? SelectMultipleBasePage&lt;ListItems&gt; multiPage =new SelectMultipleBasePage&lt;ListItems&gt;(items); multiPage.OnImageSelected += RecordingListPage_OnImageSelected; 不工作。错误:Error 20 Member 'AppName.SelectMultipleBasePage&lt;AppName.ListItems&gt;.OnImageSelected' cannot be accessed with an instance reference; qualify it with a type name instead 那么你在某处有一个static 是的,很抱歉这是我的错误。但现在我可以访问该事件,但在加载页面时获得System.NullReferenceException: Object reference not set to an instance of an object。我会调试看看哪里出错了。

以上是关于如何为自定义控件创建事件的主要内容,如果未能解决你的问题,请参考以下文章

如何为 SFSafariViewController 中的控件设置自定义字体?

我在wpf中使用了自定义的控件,请问如何为自定义控件中的一个按钮设置快捷键?

创建扩展/自定义控件的方法

XAML: 自定义控件中事件处理的最佳实践

XAML: 自定义控件中事件处理的最佳实践

如何在自定义用户控件上创建单击事件?