0

I'm creating a WPF Molds app that contains 2 windows: MainWindow with DataGrid, and AddEditWindow which allows to Add/Edit Molds.

I have a EditButton which located in a TemplateColumn of DataGrid:

<DataGridTemplateColumn Width="Auto">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                                <Button Width="150" 
                                    Height="40" 
                                    BorderThickness="2" 
                                    BorderBrush="DarkRed"
                                    Background="Red" 
                                    Foreground="White" 
                                    Content="Edit" 
                                    Name="BtnEdit"                                   
                                    CommandParameter="{Binding}"
                                    Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.AddEditWindowCommand}">
                                </Button>
                            </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>

AddEditWindowCommand:

public ICommand AddEditWindowCommand { get; }

        private bool CanAddEditWindowCommandExecute(object SelectedRow) => true;

        private void OnAddEditWindowCommandExecuted(object SelectedRow)
        {
            
            AddEditWindow window = new AddEditWindow();
            window.Show();
        }

And I want to pass DataContext to the AddEditWindowViewModel. In a Code-Behind, I could done something like this:

private void BtnEdit_Click(object sender, RoutedEventArgs e)
        {
            AddEditWindow addEditWindow = new AddEditWindow((sender as Button).DataContext as Molds);
            addEditWindow.Show();
        }

And then retrieve it AddEditWindow like this:

private Molds _currentMold = new Molds();
        public GamesEdit(Molds selectedMold)
        {
            InitializeComponent();
            if (selectedMold != null)
            {
                _currentMold = selectedMold;
                              
            }

            DataContext = _currentMold;

But in MVVM I can't. So, is there a way to do it without breaking MVVM pattern?

p.s. since I'm new to the MVVM, I would really appreciate detailed explanation.

update:

MainWindowViewModel:

internal class MainWindowViewModel : ViewModel
    {
      
        #region Variables
       
        #region Textblocks for search

        private Molds newMolds { get; set; } = new Molds();
        public string TxtType
        {
            get => newMolds.Type;
            set => newMolds.Type = value;
        }

        public string TxtName
        {
            get => newMolds.Name;
            set => newMolds.Name = value;
        }

        public string TxtKus
        {
            get => newMolds.Kus;
            set => newMolds.Kus = value;
        }

        #endregion

        #region AllMolds

        private ObservableCollection<Molds> allMolds = new ObservableCollection<Molds>(ApplicationContext.GetContext().Molds.ToList());
        public ObservableCollection<Molds> AllMolds
        {
            get => allMolds; 
            set => allMolds = value;           
        }

        #endregion

        #region FilteredMolds

        private ObservableCollection<Molds> filteredMolds = new ObservableCollection<Molds>(ApplicationContext.GetContext().Molds.ToList());
        public ObservableCollection<Molds> FilteredMolds
        {
            get
            {               
                filteredMolds = AllMolds;              
                var currentfilteredmolds = new List<Molds>(filteredMolds);
                if (TxtName != null)
                    currentfilteredmolds = currentfilteredmolds.Where(p => p.Name.ToLower().Contains(TxtName.ToLower())).ToList();
                if (TxtType != null)
                    currentfilteredmolds = currentfilteredmolds.Where(p => p.Type.ToLower().Contains(TxtType.ToLower())).ToList();
                if (TxtKus != null)
                    currentfilteredmolds = currentfilteredmolds.Where(p => p.Kus.ToLower().Contains(TxtKus.ToLower())).ToList();
                return new ObservableCollection<Molds>(currentfilteredmolds);
            }
            
            set => filteredMolds = value;
        }

        #endregion

        
        #endregion

        #region Commands

        #region CloseApplicationCommand
        public ICommand CloseApplicationCommand { get; }

        private bool CanCloseApplicationCommandExecute(object p) => true;

        private void OnCloseApplicationCommandExecuted(object p)
        {
            Application.Current.Shutdown();
        }
        #endregion

        #region SearchCommand

        public ICommand SearchCommand { get; }

        private bool CanSearchCommandExecute(object p) => true;

        private void OnSearchCommandExecuted(object p)
        {            
            OnPropertyChanged("FilteredMolds");
        }

        #endregion

        #region Open AddEditWindowCommand
        public ICommand AddEditWindowCommand { get; }

        private bool CanAddEditWindowCommandExecute(object SelectedRow) => true;

        private void OnAddEditWindowCommandExecuted(object SelectedRow)
        {
            
            AddEditWindow window = new AddEditWindow();
            window.Show();
        }
        #endregion

        #region DeleteMoldCommand

        public ICommand DeleteMoldCommand { get; }

        private bool CanDeleteMoldCommandExecute(object SelectedItems)
        {
            if (SelectedItems != null) return true; else return false;           
        }
       
        private void OnDeleteMoldCommandExecuted(object SelectedItems)
        {
            System.Collections.IList items = (System.Collections.IList)SelectedItems;
            var moldsforRemoving = items?.Cast<Molds>().ToList();

            if (MessageBox.Show($"You want to remove the following {moldsforRemoving.Count()} molds?", "Attention",
                MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
            {
                try
                {
                    ApplicationContext.GetContext().Molds.RemoveRange(moldsforRemoving);
                    ApplicationContext.GetContext().SaveChanges();
                    MessageBox.Show("Data deleted successfully.", "Data deletion",
         MessageBoxButton.OK, MessageBoxImage.Information);
                    AllMolds = new ObservableCollection<Molds>(ApplicationContext.GetContext().Molds.ToList());
                    OnPropertyChanged("FilteredMolds");
                }

                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message.ToString(), "Error",
         MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        #endregion

        #region DragMoveCommand

        public ICommand DragMoveCommand { get; }

        private bool CanDragMoveCommandExecute(object p) => true;

        private void OnDragMoveCommandExecuted(object p)
        {
            Application.Current.MainWindow.DragMove();
        }

        #endregion

        #endregion

        public MainWindowViewModel()
        {
            #region Command Samples

            CloseApplicationCommand = new LamdaCommand(OnCloseApplicationCommandExecuted, CanCloseApplicationCommandExecute);
            SearchCommand = new LamdaCommand(OnSearchCommandExecuted, CanSearchCommandExecute);
            AddEditWindowCommand = new LamdaCommand(OnAddEditWindowCommandExecuted, CanAddEditWindowCommandExecute);
            DeleteMoldCommand = new LamdaCommand(OnDeleteMoldCommandExecuted, CanDeleteMoldCommandExecute);
            DragMoveCommand = new LamdaCommand(OnDragMoveCommandExecuted, CanDragMoveCommandExecute);


            #endregion

            #region Variable Samples for searching

            TxtName = null;
            TxtKus = null;
            TxtType = null;

            #endregion
          
        }
    }

AddEditWindowViewModel

internal class AddEditWindowViewModel : ViewModel
    {
        #region Variables     


        private Molds _currentMold = new Molds();
    

        #endregion

        #region Commands

        #region CloseWindowCommand
        public ICommand CloseWindowCommand { get; }

        private bool CanCloseWindowCommandExecute(object p) => true;

        private void OnCloseWindowCommandExecuted(object p)
        {
            Application.Current.Windows[1].Close();           
        }
        #endregion

        #region DragMoveAddEditWindowCommand

        public ICommand DragMoveAddEditWindowCommand { get; }

        private bool CanDragMoveAddEditWindowCommandExecute(object p) => true;

        private void OnDragMoveAddEditWindowCommandExecuted(object p)
        {
            Application.Current.Windows[1].DragMove();
        }

        #endregion

        #endregion

        public AddEditWindowViewModel()
        {
            
            #region Command samples

            CloseWindowCommand = new LamdaCommand(OnCloseWindowCommandExecuted, CanCloseWindowCommandExecute);
            DragMoveAddEditWindowCommand = new LamdaCommand(OnDragMoveAddEditWindowCommandExecuted, CanDragMoveAddEditWindowCommandExecute);

            #endregion

        }
    }

I connect them to the window using <Window.DataContext>:

<Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>

Same goes for the AddEditWindowViewModel.

DataGrid binding:

<DataGrid x:Name="DGridMolds" 
                  AutoGenerateColumns="False" 
                  IsReadOnly="True" 
                  Foreground="White" 
                  BorderBrush="White" 
                  Background="#2b2a38"
                  Grid.Column="1"
                  Grid.Row="1"
                  ItemsSource="{Binding Path=FilteredMolds}"
                  >

AddEditWindow.Xaml:

<Window.DataContext>
        <vm:AddEditWindowViewModel/>
    </Window.DataContext>

    <Border Background="#2f2e3c" 
            CornerRadius="10">

        <Border.InputBindings>
            <MouseBinding Command="{Binding DragMoveAddEditWindowCommand}" MouseAction="LeftClick"/>
        </Border.InputBindings>

        <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition Width="30"/>
        </Grid.ColumnDefinitions>

            <StackPanel Grid.Column="1" Grid.Row="1" Orientation="Vertical">
                <TextBlock Text="Add" FontSize="22" Foreground="White" HorizontalAlignment="Center" Margin="0,30,0,0"/>
                <TextBox Foreground="White" Text="{Binding Type, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="5, 35, 5, 5" materialDesign:HintAssist.Hint="Type"/>
                <TextBox Foreground="White" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="5" materialDesign:HintAssist.Hint="Name"/>
                <TextBox Foreground="White" Text="{Binding Kus, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="5" materialDesign:HintAssist.Hint="Kus"/>
  • I do not recommend creating internal classes for use in WPF. There may be problems. WPF doesn't work well with nested classes either. For WPF, always make public classes in the namespace. – EldHasp Jun 14 '21 at 07:59
  • I don't understand the function of the AddEditWindowViewModel class. It has only two teams. There are no data properties in it. What then should AddEditWindow display? – EldHasp Jun 14 '21 at 08:03
  • Teams? You mean commands? AddEditWindow should display the Window with TextBoxes, that are binded to Molds class properties like Name, Type, Kus, etc. I haven't declared them yet tho. Also, the AddEditWindow have SaveButton, which will be binded to SaveCommand that are creates Molds sample where are each class property is assigned to each variable received from TextBoxes. – Maels Lafnegal Jun 14 '21 at 08:51
  • I am writing about the AddEditWindowViewModel class that should be used for an edit view. It has only two commands CloseWindowCommand and DragMoveAddEditWindowCommand. And there is absolutely no data that the View should edit. – EldHasp Jun 14 '21 at 09:01
  • AddEditWIndowViewModel should get the DataContext from button which opens AddEditWindow via AddEditWindowCommand and then display in TextBoxes properties of DataContext like Name, Type, Kus, etc. I added XAML markup of AddEditWindow to the post. – Maels Lafnegal Jun 14 '21 at 09:44

1 Answers1

0

In a Code-Behind, I could done something like this:

Take a closer look at your XAML.
You have a binding set in the CommanParameter property.
The binding instance is empty - this means that the binding occurs to the DataContext of the element in which it is specified.
Hence, in the command parameter, you will get this Data Context.

        private void OnAddEditWindowCommandExecuted(object SelectedRow)
        {
            AddEditWindow window = new AddEditWindow()
                    {DataContext = SelectedRow};
            window.Show();
        }

So, is there a way to do it without breaking MVVM pattern?

You've already broken MVVM.
ViewModel is not allowed to work with UI elements.
The ViewModel doesn't even need to know what type of View is using it: WPF, Forms, or Console.
And you CREATE the Window!
This is unacceptable for the MVVM concept.

Addition in connection with showing more detailed code in the main question:

I can't understand the logic of your code. Therefore, I will write in the measure as I understand your intentions.

AddEditWindowViewModel class - designed for logic for editing and/or adding an item. But then he has to get this element and provide it in his property so that he can create a GUI for editing.

It should be something like this:

namespace MoldsApp
{
    public class AddEditWindowViewModel : ViewModel
    {
        public Molds CurrentMold { get; }

        // Constructor called to edit an entity
        public AddEditWindowViewModel(Molds currentMold)
        {
            CurrentMold = currentMold;
        }

        //Constructor called to create and edit an entity
        public AddEditWindowViewModel()
            : this(new Molds())
        {
        }
    }
}

Also, your DataContext is set incorrectly.
By creating it inside XAML, you cannot set the data for this instance of the ViewModel.
Therefore, in XAML, you can instantiate a ViewModel used only at the time of its designed.
For a design-time DataContext, use the d: prefix.

    <d:Window.DataContext>
        <vm:MainWindowViewModel/>
    </d:Window.DataContext>

With these changes, the item edit command should be like this:

    private void OnAddEditWindowCommandExecuted(object SelectedRow)
    {
        AddEditWindow window = new AddEditWindow()
        {
             DataContext =  new AddEditWindowViewModel((Molds)SelectedRow)
        };
        window.Show();
    }
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • So, i figured out that i should pass DataContext not to window, but ViewModel. Could you provide some code example of passing DataContext between ViewModels? Also, i would really appreciate if u could hint how i can get rid of AddEditWindow window = new AddEditWindow(), and open new Window MVVM'ish way. – Maels Lafnegal Jun 13 '21 at 18:52
  • Are you using any package to implement MVVM? If so, then such packages usually have built-in tools for setting the View's reaction to some commands from the ViewModel. – EldHasp Jun 13 '21 at 18:57
  • No, i'm not using any package. – Maels Lafnegal Jun 13 '21 at 18:59
  • Then it will take a lot of explanations not related to the question of the topic. And I cannot give them in the format of comments. Need to give answers with sample code. But such responses, which are not relevant to the original question of the topic, are not welcomed by the policies adopted here. Did my suggested solution help with your question? If so, mark the topic as having a solution. Then create a new topic with a question on how to create a new Window from the ViewModel without breaking MVVM and without using additional packages. – EldHasp Jun 13 '21 at 19:42
  • Nope, it's not helped. – Maels Lafnegal Jun 13 '21 at 19:59
  • The implementation shown is equivalent for the ViewModel of your BtnEdit_Click method. As I understood from your code, you need to pass the element on which the button was clicked to the data context of the new window. Put a breakpoint on the command. And check what came to the command in the parameter. – EldHasp Jun 13 '21 at 20:04
  • AddEditWindowCommand declared in MainWindowViewModel, not AddEditWindowViewModel. – Maels Lafnegal Jun 13 '21 at 20:26
  • So you have your own VM for AddEditWindow? Add details to the question of which windows have which VM, how do you create them and connect them to the window, to which collection the DataGrid is bound, where the string on which the button placed should be transferred. – EldHasp Jun 13 '21 at 20:42
  • Read the supplement to my answer. – EldHasp Jun 14 '21 at 08:38
  • As I wrote earlier, you have a lot of flaws in complying with MVVM. But the policy adopted here does not welcome answers not on the main question of the topic. Create new topics for questions: dialog editing window, window creation from ViewModel, etc. In these topics, I will give you more detailed explanations. – EldHasp Jun 14 '21 at 10:05