简体   繁体   中英

How can I access some properties in userControl from another class?

My program is consists of a userControl and a class in C# winform environment. when I click on a button in userControl , an instance of my class is created and the main process starts. I want to interact with my class with userControl through the program is running. for example, I want to show some massages from class to a textBox property in the designed userControl . I can not instantiate from my userControl in my class because it is wrong and can not access running userControl properties. So, How can I do it? Is it a good solution to set userControl a singleton class? Do u recommend a better solution?

Edit: For more clarification: I want to call a method from the class and pass an argument ( string ) into it. Then textBox in the userControl should show the passed argument! This procedure would be repeated whenever it has an output in the class !

Update: here is code sample: In the class definition:

class MyClass
{
    public MyClass()
    {
        //do sth
        if (a condition)
        {
            //It is wrong to create an instance but I want to explain my purpose. It is what I need!
            searchPanel sp = new searchPanel();
            sp.showMessage("...");
        }
    }
}

And here is userControl:

public partial class searchPanel : UserControl
{
    public searchPanel()
    {
        InitializeComponent();
    }
    public void showMessage(string message)
    {
        textBox.AppendText(message);
        textBox.AppendText(Environment.NewLine);
    }
}

So, How can I do it?

You could do this by implementing events . This way you can signal every class that is subscribing to your " class " events when whatever you want happens. Imagine your " class " just completes to sum up 10 integer numbers; you fire a event informing this operation just happened. Your " userControl " class is subscribing to this event and will be able to execute a function to change whatever control in the form you want. By using events you keep class isolation, avoiding things like global state in your code.

Is it a good solution to set userControl a singleton class?

No, you should avoid using singleton as a pattern in your code. Above all using singleton as global state to expose your " class " to " userControl " is probably evidence of code smell

This is usually done using the WPF Command Pattern and binding ( Data Binding Overview ). This is the recommended approach.

MainWindow.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

  <SearchPanel />
</Window>

SearchPanel.xaml

<UserControl>
  <UserControl.DataContext>
    <ViewModel />
  </UserControl.DataContext>

  <StackPanel>

    <Button Command="{Binding ShowMessageCommand}" />

    <TextBlock Text="{Binding Message}" />
  <StackPanel>
</UserControl>

SearchPanel.xaml.cs

public partial class SearchPanel : UserControl
{
  public SearchPanel()
  {
    InitializeComponent();
  } 
}

ViewModel.cs

class ViewModel : INotifyPropertyChanged
{
  private string message;
  public string Message
  {
    get => this.message;
    set
    {
      this.message = value;
      OnPropertyChanged();
    }
  }

  ICommand ShowMessageCommand => new RelayCommand(CreateMessage);


  // Command callback
  private void CreateMessage()
  {
    this.Message = "This is a message for display in the UI";
  }

  public event PropertyChangedEventHandler PropertyChanged;

  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

The helper class to create a reusable command:

RelayCommand.cs

/// <summary>
/// An implementation independent ICommand implementation.
/// Enables instant creation of an ICommand without implementing the ICommand interface for each command.
/// The individual Execute() an CanExecute() members are supplied via delegates.
/// <seealso cref="System.Windows.Input.ICommand"/>
/// </summary>
/// <remarks>The type of <c>RelayCommand</c> actually is a <see cref="System.Windows.Input.ICommand"/></remarks>
public class RelayCommand : ICommand
{
  protected readonly Func<Task> executeAsyncNoParam;
  protected readonly Action executeNoParam;
  protected readonly Func<bool> canExecuteNoParam;
  private readonly Func<object, Task> executeAsync;
  private readonly Action<object> execute;
  private readonly Predicate<object> canExecute;
  /// <summary>
  /// Raised when RaiseCanExecuteChanged is called.
  /// </summary>
  public event EventHandler CanExecuteChanged
  {
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
  }
  /// <summary>
  /// Creates a new command that can always execute.
  /// </summary>
  /// <param name="execute">The execution logic.</param>
  public RelayCommand(Action<object> execute)
      : this(execute, (param) => true)
  {
  }
  /// <summary>
  /// Creates a new command that can always execute.
  /// </summary>
  /// <param name="executeNoParam">The execution logic.</param>
  public RelayCommand(Action executeNoParam)
      : this(executeNoParam, () => true)
  {
  }
  /// <summary>
  /// Creates a new command that can always execute.
  /// </summary>
  /// <param name="executeAsync">The awaitable execution logic.</param>
  public RelayCommand(Func<object, Task> executeAsync)
      : this(executeAsync, (param) => true)
  {
  }
  /// <summary>
  /// Creates a new command that can always execute.
  /// </summary>
  /// <param name="executeAsyncNoParam">The awaitable execution logic.</param>
  public RelayCommand(Func<Task> executeAsyncNoParam)
      : this(executeAsyncNoParam, () => true)
  {
  }
  /// <summary>
  /// Creates a new command.
  /// </summary>
  /// <param name="executeNoParam">The execution logic.</param>
  /// <param name="canExecuteNoParam">The execution status logic.</param>
  public RelayCommand(Action executeNoParam, Func<bool> canExecuteNoParam)
  {
    this.executeNoParam = executeNoParam ?? throw new ArgumentNullException(nameof(executeNoParam));
    this.canExecuteNoParam = canExecuteNoParam;
  }
  /// <summary>
  /// Creates a new command.
  /// </summary>
  /// <param name="execute">The execution logic.</param>
  /// <param name="canExecute">The execution status logic.</param>
  public RelayCommand(Action<object> execute, Predicate<object> canExecute)
  {
    this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
    this.canExecute = canExecute;
  }
  /// <summary>
  /// Creates a new command.
  /// </summary>
  /// <param name="executeAsyncNoParam">The awaitable execution logic.</param>
  /// <param name="canExecuteNoParam">The execution status logic.</param>
  public RelayCommand(Func<Task> executeAsyncNoParam, Func<bool> canExecuteNoParam)
  {
    this.executeAsyncNoParam = executeAsyncNoParam ?? throw new ArgumentNullException(nameof(executeAsyncNoParam));
    this.canExecuteNoParam = canExecuteNoParam;
  }
  /// <summary>
  /// Creates a new command.
  /// </summary>
  /// <param name="executeAsync">The awaitable execution logic.</param>
  /// <param name="canExecute">The execution status logic.</param>
  public RelayCommand(Func<object, Task> executeAsync, Predicate<object> canExecute)
  {
    this.executeAsync = executeAsync ?? throw new ArgumentNullException(nameof(executeAsync));
    this.canExecute = canExecute;
  }
  /// <summary>
  /// Determines whether this RelayCommand can execute in its current state.
  /// </summary>
  /// <returns>true if this command can be executed; otherwise, false.</returns>
  public bool CanExecute()
  {
    return this.canExecuteNoParam == null || this.canExecuteNoParam();
  }

  /// <summary>
  /// Executes the RelayCommand on the current command target.
  /// </summary>
  public async void Execute()
  {
    if (this.executeAsyncNoParam != null)
    {
      await ExecuteAsync();
      return;
    }
    this.executeNoParam();
  }

  /// <summary>
  /// Executes the RelayCommand on the current command target.
  /// </summary>
  public async Task ExecuteAsync()
  {
    if (this.executeAsyncNoParam != null)
    {
      await this.executeAsyncNoParam();
      return;
    }

    await Task.Run(() => this.executeNoParam());
  }
  /// <summary>
  /// Determines whether this RelayCommand can execute in its current state.
  /// </summary>
  /// <param name="parameter">
  /// Data used by the command. If the command does not require data to be passed, 
  /// this object can be set to null.
  /// </param>
  /// <returns>true if this command can be executed; otherwise, false.</returns>
  public bool CanExecute(object parameter)
  {
    return this.canExecute == null || this.canExecute(parameter);
  }

  /// <summary>
  /// Executes the RelayCommand on the current command target.
  /// </summary>
  /// <param name="parameter">
  /// Data used by the command. If the command does not require data to be passed, 
  /// this object can be set to null.
  /// </param>
  public async void Execute(object parameter)
  {
    if (parameter == null)
    {
      Execute();
      return;
    }

    if (this.executeAsync != null)
    {
      await ExecuteAsync(parameter);
      return;
    }
    this.execute(parameter);
  }

  /// <summary>
  /// Executes the RelayCommand on the current command target.
  /// </summary>
  /// <param name="parameter">
  /// Data used by the command. If the command does not require data to be passed, 
  /// this object can be set to null.
  /// </param>
  public async Task ExecuteAsync(object parameter)
  {
    if (this.executeAsync != null)
    {
      await this.executeAsync(parameter);
      return;
    }

    await Task.Run(() => this.execute(parameter));
  }
}

I recommend some reading on MVVM and Commanding eg Basic MVVM and ICommand Usage Example or Commanding Overview


Alternative approach using a simple Button.Click event handler:

SearchPanel.xaml

<UserControl>
  <UserControl.DataContext>
    <ViewModel />
  </UserControl.DataContext>

  <StackPanel>

    <Button Click="ShowMessage_OnButtonClick" />

    <TextBlock x:Name="MessageTextBlock" />
  <StackPanel>
</UserControl>

SearchPanel.xaml.cs

public partial class SearchPanel : UserControl
{
  public SearchPanel()
  {
    InitializeComponent();
  } 

  private void ShowMessage_OnButtonClick(object sender, RoutedEventArgs e)
  {
    this.MessageTextBlock = "This is a message for display in the UI";
  }
}

Events and XAML code-behind

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM