Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
264 views
in Technique[技术] by (71.8m points)

c# - Adding/removing rows of controls

I have a panel and my idea is to have it populated by a stack panel containing two text boxes. When the user enters something in the left box, something should be generated in the right one, as follows.

<StackPanel Orientation="Horizontal">
  <TextBox Name="Lefty" LostFocus="FillMyBuddy" />
  <TextBox Name="Righty" LostFocus="FillMyBuddy" />
</StackPanel>

However, I'd like to add an option to add/remove rows and, since I wish not to limit myself to the number of such, I get a bit uncertain regarding the approach on two points.

  1. Manipulating DOM (well, it's XAML/WPF but you see what I'm aiming at).
  2. Event handling.

Is it a big no-no to programmatically affect the mark-up structure of the window? Or is it OK to add/remove panels during run-time?

What would the recommended way to be if I want the Lefty number 3 change stuff in Righty number 3? Anything more neat than checking the sender and pulling its siblings from the parent? I want to use a single event handler for any and all rows (knowing that the operations are always intra-row-wise).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

You will want to follow MVVM, and have no code in your code-behind (programmatically affect the mark-up structure) files. The concept is easy when you grasp it, so learn it before you start writing your code.

In short, you are going to want to have a view model (something that implements INotifyPropertyChanged (INPC)) which holds your collection of items (which are going to be models, or view models in pure-MVVM). In "hybrid"-MVVM you could just have your models implement INPC.

Then, through the use of commands, you'd implement the logic to remove items from the list that its in. You can pass references, raise notification, using event bubbling, etc. (it's your preference) to have the item actually removed. In my case, I just passed a "manager" to the hybrid-model and held a reference to that. When the command is called (button is clicked), the model calls for the reference to remove itself from the list.

After you do that you define a DataTemplate to define what an "item" should look like one the View. You use a ItemsControl to show a collection of items, and bind to its ItemsSource so the collection of items are shown. Set your ItemsControl.ItemTemplate to the DataTemplate you created, and anything added to the collection bound to ItemsSource of the type defined in DataTemplate.DataType will render as you specify in the DataTemplate.

At the end of the day, you should learn about MVVM design, DataContext, INPC, Commands, Control types and their "main" properties, e.g. everything that inherits from ItemsControl has an ItemsSource property.

Here is a working example, where changing the original string, will reverse it and put it in the read-only right side text box:

Text changed, items removed, etc.

MainWindow.xaml.cs (code-behind)

public partial class MainWindow : Window
{
    StructureVm _struct = new StructureVm("Test");

    public MainWindow()
    {
        InitializeComponent();

        DataContext = _struct;
    }
}

MainWindow.xaml (View)

<Window x:Class="DataTemplateWithCommands.MainWindow"
        xmlns:local="clr-namespace:DataTemplateWithCommands"
        Title="MainWindow" Height="350" Width="525" Background="Orange">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:Model}"
                      x:Key="VmItem">
            <StackPanel Orientation="Horizontal">
                <TextBox Text="{Binding Original, UpdateSourceTrigger=PropertyChanged}" />
                <TextBox Text="{Binding Encoded}"
                         IsReadOnly="True" />
                <Button Content="X"
                        Command="{Binding RemoveMeCommand}" />
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ItemsControl ItemsSource="{Binding Items}"
                      ItemTemplate="{StaticResource VmItem}">
        </ItemsControl>
    </Grid>
</Window>

Interface (helpful for Dependency Injection)

public interface IStructureManager
{
    bool RemoveItem(Model itemToRemove);
}

ViewModel

public class StructureVm : IStructureManager
{
    private readonly ObservableCollection<Model> _items;
    private readonly string _title;

    public StructureVm(string title)
    {
        _title = title;
        _items = new ObservableCollection<Model>
            {
                new Model(this, "12"),
                new Model(this, "23"),
                new Model(this, "34"),
                new Model(this, "45"),
                new Model(this, "56"),
                new Model(this, "67"),
                new Model(this, "78"),
                new Model(this, "89"),
            };
    }}

    public ObservableCollection<Model> Items
    {
        get
        {
            return _items;
        }
    }

    public string Title
    {
        get
        {
            return _title;
        }
    }

    public bool RemoveItem(Model itemToRemove)
    {
        return _items.Remove(itemToRemove);
    }
}

Model (not pure-MVVM, pure MVVM models don't implement INPC, and don't have Command in them)

public class Model : INotifyPropertyChanged
{
    private readonly RelayCommand _removeMe;
    private string _original;
    private string _encoded;
    private readonly IStructureManager _manager;
    public string Original
    {
        get
        {
            return _original;
        }
        set
        {
            _original = value;
            Encoded = ReverseString(_original);
            NotifyPropertyChanged();
        }
    }

    public string Encoded
    {
        get
        {
            return _encoded;
        }
        set
        {
            _encoded = value;
            NotifyPropertyChanged();
        }
    }

    public ICommand RemoveMeCommand
    {
        get
        {
            return _removeMe;
        }
    }

    public Model(IStructureManager manager, string original)
    {
        Original = original;
        _manager = manager;
        _removeMe = new RelayCommand(param => RemoveMe(), param => CanRemoveMe);
    }

    private void RemoveMe()
    {
        _manager.RemoveItem(this);
    }

    private bool CanRemoveMe
    {
        get
        {
            //Logic to enable/disable button
            return true;
        }
    }

    private string ReverseString(string s)
    {
        char[] arr = s.ToCharArray();
        Array.Reverse(arr);
        return new string(arr);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

RelayCommand implementation

From here on out all you have to do is change the attributes of your controls to whatever you're happy with and call it good. The example might be ugly, but I'm leaving it as an exercise for you to figure out other properties/attributes of WPF controls.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...