Click here to Skip to main content
15,918,268 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.
 
Share this answer
 
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.
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>
 
Share this answer
 

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