Click here to Skip to main content
15,349,321 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I have three buttons with different names. I want to pass the text of the pressed button into the ViewModel and fill the value of the TabName property. How can I do that? In the following code, the TextBlock works as a button.

What I have tried:

View:
<TextBlock Margin="4" Width="auto" Text="Options">
<TextBlock.InputBindings>
    <MouseBinding Command="{Binding NewTabCommand}" CommandParameter="{Binding TabName}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>


<TextBlock Margin="4" Width="auto" Text="Equipments">
<TextBlock.InputBindings>
    <MouseBinding Command="{Binding NewTabCommand}" CommandParameter="{Binding TabName}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>


<TextBlock Margin="4" Width="auto" Text="Orders">
<TextBlock.InputBindings>
    <MouseBinding Command="{Binding NewTabCommand}" CommandParameter="{Binding TabName}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>


ViewModel:


private ICommand _newTabCommand;

public ICommand NewTabCommand
{
    get 
    {
        if(_newTabCommand == null)
        {
            return new TabActionCommand(p => NewTab(TabName));
        }
            return _newTabCommand;
    }
    set { _newTabCommand = value; }
}
public string TabName { get; set; }
private void NewTab(string tabName)
{
    Tabs.Add(new TabViewViewModel(tabName));
}
Posted
Updated 8-May-22 4:28am

The trick with MVVM is the View is a visual representation of the data. The ViewModel is the interface between the data and the view.

So for the Tabs to reflect the Button clicks, you should be binding the Tabs to the data and the Button clicks are bound to the Command that passes a CommandParameter to the ViewModel. Then the ViewModel updates the data and the binding will notify the View of the changes, then the View will refresh the UI.

I'm not quite sure which framework you are using or how you're wiring up your binding, but here is a minimal solution to do what you are asking.

So first we need to set up the Command to enable the Button in the View to pass data to the ViewModel:
C#
public abstract class CommandBase : ICommand
{
    public event EventHandler? CanExecuteChanged;

    public virtual bool CanExecute(object? parameter) => true;

    public abstract void Execute(object? parameter);

    protected void OnCanExecuteChanged()
        => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

public class ButtonClickCommand : CommandBase
{
    public ButtonClickCommand(Action<string> callback)
        => _callback = callback;

    private readonly Action<string>? _callback;

    public override void Execute(object? parameter)
        => _callback?.Invoke(parameter as string ?? string.Empty);
}

Next we need to set up the ViewModel to:
1. Expose an ObservableCollection for the Tabs
2. Expose and wire up then the Command for the Buttons
3. A method to update the Tabs collection with the new Tabs as the Buttons are clicked
C#
public class MainViewModel
{
    public MainViewModel()
        => ButtonClickCommand = new ButtonClickCommand(OnClicked);

    public ICommand? ButtonClickCommand { get; }

    public ObservableCollection<string> Tabs { get; } = new();

    private void OnClicked(string buttonName)
        => Tabs.Add(buttonName);
}

Now we set the DataConext with the ViewModel for the View:
C#
public partial class MainWindow : Window
{
    public MainWindow()
    {
        DataContext = new MainViewModel();
        InitializeComponent();
    }
}

Lastly, we can now wire up the view:
XML
<StackPanel Orientation="Horizontal">
    <StackPanel.Resources>
        <Style TargetType="Button">
            <Setter Property="Padding" Value="20 10" />
            <Setter Property="Margin" Value="5 10"/>
        </Style>
    </StackPanel.Resources>

    <Button Content="Button 1"
            Command="{Binding ButtonClickCommand}"
            CommandParameter="Button 1" />
    <Button Content="Button 2"
            Command="{Binding ButtonClickCommand}"
            CommandParameter="Button 2" />
    <Button Content="Button 3"
            Command="{Binding ButtonClickCommand}"
            CommandParameter="Button 3" />
</StackPanel>

<TabControl Grid.Row="1" Height="100"
            ItemsSource="{Binding Tabs}">
</TabControl>

When you run the code, each Button click will notify the ViewModel via the Command and a new Tab will be added to the Tabs collection. The Tabs colllection will notify the View via the binding and the Tabs control will add the new Tab.

This is the bare minimum to getting it working. The rest is up to you.

Hope this answers your question.
   
v2
Comments
Code4Ever 8-May-22 23:57pm
   
I'm using .Net 6
Your method is:
private void OnClicked(string buttonName)
=> Tabs.Add(buttonName);

While your constructor is:
public MainViewModel()
=> ButtonClickCommand = new ButtonClickCommand(OnClicked);

`OnClicked` needs an input parameter in the constructor.
Graeme_Grant 9-May-22 0:07am
   
Did you try it?

BTW, here is my Target Framework for the working example:
<TargetFramework>net6.0-windows</TargetFramework>

It will work for both Frameworks... ;)
Graeme_Grant 9-May-22 0:18am
   
`OnClicked` needs an input parameter in the constructor

And that it does. I'm using method Group syntax. The compiler does all the heavy lifting. Here is the old syntax that is the equivilent of the method Group syntax:
ButtonClickCommand = new ButtonClickCommand(s => OnClicked(s));
Code4Ever 9-May-22 0:27am
   
I'm trying to delete all my codes and test your code. I'll tell you the result. Thanks.
Graeme_Grant 9-May-22 0:28am
   
DO what I did, start a new project to prototype/test with...You can have more than one project in your solution.
Code4Ever 9-May-22 0:42am
   
Thanks. It worked in my project. How can I specify specific content for each tab? I mean, I want to load special content for each tab created by button 1 and special content for the tab created by button 2, and so on.
Graeme_Grant 9-May-22 0:47am
   
This is a new question. You should create a new one.

But quickly, you need to invert your thinking to Data before UI. I touch on this in my answer. Yuo need to use a model to hold the key (Tab Id) and the ViewModel properties. Then the Tab control will look at the data and use the ViewModel in the Model to update the UI. For this to work, you set up resource entries to link the View to the ViewModel.

As I said, this is a new question beyound the scope of the question above.
Code4Ever 9-May-22 1:29am
   
A problem with the current code is that when you add a duplicate tab, you cannot select it. The first tab (related to that button) is selected. How can I fix it?
Graeme_Grant 9-May-22 1:33am
   
Create a new question with how to dynamically bind a viewmodel to a TabIten content area and I will answer both questions for you.

TL;DR: The answer is just above, but without code...
I think the best is using
EventTrigger 
Requirements: Microsoft.Xaml.Behaviors.Wpf (NuGet package):

like it is shown in my article (in Vb.Net but same should be possible with C#):
VB.NET MVVM Toolkit Demo[^]
Example:
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
...
<Window.DataContext>
        <local:TestingViewModel/>
    </Window.DataContext>
                
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="Closing">
            <b:InvokeCommandAction Command="{Binding ExitApp, Mode=OneWay}"/>
        </b:EventTrigger>
        <b:EventTrigger EventName="Loaded">
            <b:InvokeCommandAction Command="{Binding Apploaded, Mode=OneWay}"/>
        </b:EventTrigger>
    </b:Interaction.Triggers>
   

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900