简体   繁体   中英

Binding data from viewmodel to view

I am new to wpf mvvm .

I need to bind data back to the view property from viewmodel. In the model I have declared all the properties and in the viewmodel I declared properties using getter and setter.

When am trying to bind the values, it is not updating the view but values get binded to the properties. The controls are inside a grid. Controls are not binding inside a grid. I need to bind data to the controls when am clicking on the row of a data grid. I am getting values but it is not get binded to the controls.

Please help me immediately.

This is my model.

public string FirstName{get;set;}        
public string LastName { get; set; }
public DateTime Dob { get; set; }
public int Age { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public string Email { get; set; }
public string Web { get; set;}

This is my view model,

public string Emp_ID;
public string emp_ID {
    get {
        return emp_ID;
    }
    set {
        emp_ID = value;
        OnPropertyChanged("Emp_ID");
    }
}
public string FirstName {
    get {
        return employee.FirstName;
    }
    set {
        employee.FirstName = value;
        OnPropertyChanged("FirstName");
        this.DetailView = new HomeViewModel(value);
    }
}

public string LastName {
    get {
        return employee.LastName;
    }
    set {
        employee.LastName = value;
        OnPropertyChanged("LastName");
    }
}
public DateTime Dob {
    get {
        return employee.Dob;
    }
    set {
        employee.Dob = value;
        OnPropertyChanged("Dob");
        OnPropertyChanged("Age");
    }
}
public int Age {
    get {
        //Birthdate = Convert.ToDateTime(Dob);
        return employee.Age = DateTime.Today.Year - Dob.Year;
    }
    set {
        employee.Age = value;
        OnPropertyChanged("Age");
    }

}
public string Street1 {
    get {
        return employee.Street1;
    }
    set {
        employee.Street1 = value;
        OnPropertyChanged("Street1");
    }
}
public string Street2 {
    get {
        return employee.Street2;
    }
    set {
        employee.Street2 = value;
        OnPropertyChanged("Street2");
    }
}
public string City {
    get {
        return employee.City;
    }
    set {
        employee.City = value;
        OnPropertyChanged("City");
    }
}
public string State {
    get {
        return employee.State;
    }
    set {
        employee.State = value;
        OnPropertyChanged("State");
    }
}
public string ZipCode {
    get {
        return employee.ZipCode;
    }
    set {
        employee.ZipCode = value;
        OnPropertyChanged("ZipCode");
    }
}
public string PhoneNumber {
    get {
        return employee.PhoneNumber;
    }
    set {
        employee.PhoneNumber = value;
        OnPropertyChanged("PhoneNumber");
    }
}
public string MobileNumber {
    get {
        return employee.MobileNumber;
    }
    set {
        employee.MobileNumber = value;
        OnPropertyChanged("MobileNumber");
    }
}
public string Email {
    get {
        return employee.Email;
    }
    set {
        employee.Email = value;
        OnPropertyChanged("Email");
    }
}
public string Web {
    get {
        return employee.Web;
    }
    set {
        employee.Web = value;
        OnPropertyChanged("Web");
    }
}

This is my binding code in the viewmodel

public ICommand SelectEmployeeCommand {
    get {
        return selectEmployeeCommand;
    }
    set {
        selectEmployeeCommand = value;
    }
}
private void selectEmployeeDetails() {
    EmployeeViewModel employeeView = new EmployeeViewModel();
    try {
        sqlConnection = new SqlConnection(Connection.connectionstring);
        sqlConnection.Open();
        selectCommand = new SqlCommand(AppConstants.StoredProcedures.GetDataProcedure, sqlConnection);
        selectCommand.CommandType = CommandType.StoredProcedure;
        selectCommand.Parameters.Add(AppConstants.Parameters.Emp_ID, SqlDbType.Int).Value = SelectedEmployee.Row.ItemArray[0];


        sqlAdapter = new SqlDataAdapter(selectCommand);
        sqlDataSet = new DataSet();
        sqlAdapter.Fill(sqlDataSet);
        employeeView.FirstName = sqlDataSet.Tables[0].Rows[0][1].ToString();
        employeeView.LastName = sqlDataSet.Tables[0].Rows[0][2].ToString();
        employeeView.Dob = Convert.ToDateTime(sqlDataSet.Tables[0].Rows[0][3].ToString());
        employeeView.Age = Convert.ToInt32(sqlDataSet.Tables[0].Rows[0][4].ToString());
        employeeView.Street1 = sqlDataSet.Tables[0].Rows[0][5].ToString();
        employeeView.Street2 = sqlDataSet.Tables[0].Rows[0][6].ToString();
        employeeView.City = sqlDataSet.Tables[0].Rows[0][7].ToString();
        employeeView.State = sqlDataSet.Tables[0].Rows[0][8].ToString();
        employeeView.ZipCode = sqlDataSet.Tables[0].Rows[0][9].ToString();
        employeeView.PhoneNumber = sqlDataSet.Tables[0].Rows[0][10].ToString();
        employeeView.MobileNumber = sqlDataSet.Tables[0].Rows[0][11].ToString();
        employeeView.Email = sqlDataSet.Tables[0].Rows[0][12].ToString();
        employeeView.Web = sqlDataSet.Tables[0].Rows[0][13].ToString();
    } catch (Exception ex) {
        throw ex;
    }
}

this is my complete xaml for the view

<Window x:Class="EmployeeRegistration.Home" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:View="clr-namespace:EmployeeRegistration" Title="Home" Height="595" Width="1096">
    <Window.DataContext>
        <View:EmployeeViewModel></View:EmployeeViewModel>
    </Window.DataContext>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <cmd:EventToCommand Command="{Binding GetEmployeeCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Window.Resources>
        <Style TargetType="Control" x:Key="myErrorTemplate">
            <Setter Property="Validation.ErrorTemplate"><Setter.Value><ControlTemplate><Border BorderBrush="Red" BorderThickness="1" CornerRadius="2.75" Grid.Column="0"><AdornedElementPlaceholder Name="MyControl" Grid.Column="0"/></Border></ControlTemplate></Setter.Value></Setter><Style.Triggers><Trigger Property="Validation.HasError" Value="true"><Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self},
 Path=(Validation.Errors)[0].ErrorContent
            }

            "/>
 </Trigger></Style.Triggers>
        </Style>
        <Style TargetType="TextBox" BasedOn="{StaticResource myErrorTemplate}" />
    </Window.Resources>
    <Grid Height="984" Width="1073">
        <Grid.RowDefinitions>
            <RowDefinition Height="65"></RowDefinition>
            <RowDefinition Height="36"></RowDefinition>
            <RowDefinition Height="35" />
            <RowDefinition Height="44"></RowDefinition>
            <RowDefinition Height="45" />
            <RowDefinition Height="49" />
            <RowDefinition Height="56" />
            <RowDefinition Height="43" />
            <RowDefinition Height="40" />
            <RowDefinition Height="37"></RowDefinition>
            <RowDefinition Height="39*" />
            <RowDefinition Height="42*" />
            <RowDefinition Height="453*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="538"></ColumnDefinition>
            <ColumnDefinition Width="121" />
            <ColumnDefinition Width="149" />
            <ColumnDefinition Width="110" />
            <ColumnDefinition Width="182" />
        </Grid.ColumnDefinitions>

        <Label Name="lblEmployeeDetails" Content="EMPLOYEE DETAILS" FontFamily="Calibri" FontSize="26" HorizontalAlignment="Center" Margin="117,12,125,6" Width="296"></Label>

        <Label Name="lblHeading" Grid.Column="1" Content="UPDATE EMPLOYEE DETAILS" FontFamily="Calibri" FontSize="26" VerticalAlignment="Center" HorizontalAlignment="Center" Height="44" Width="486" Margin="49,10,27,10" Grid.ColumnSpan="4">
            </Label>

        <TextBlock Margin="0,-2,53,21" HorizontalAlignment="Right" Width="148" Grid.Column="3" Grid.ColumnSpan="2">
            <Button Name="btnNewEmployee" Command="{Binding NewEmployeeCommand}" Content="New Employee Registration" FontFamily="Calibri" Background="#FF2693A7" Foreground="White" />
        </TextBlock>
        <Label Name="lblEnterName" Grid.Row="1" Content="Enter Employee Name:" FontFamily="Calibri" FontSize="16" Margin="0,8,12,21" Grid.RowSpan="2"></Label>
        <TextBox Name="txtSearch" Text="{Binding Path=FirstName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Grid.Row="1" Margin="174,18,0,31" HorizontalAlignment="Left" Width="186" Grid.RowSpan="2"></TextBox>
        <Button Name="btnSearch" Command="{Binding SearchEmployeeCommand}" Grid.Row="1" Content="Search" Margin="0,18,61,31" HorizontalAlignment="Right" Width="103" Foreground="White" Background="Black" Grid.RowSpan="2"></Button>

        <DataGrid Grid.Row="3" x:Name="grdEmployee" SelectedItem="{Binding SelectedEmployee, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding EmployeeDatatable, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" AutoGenerateColumns="False" IsReadOnly="True" FontFamily="Calibri" HorizontalAlignment="Center" Margin="5,5,39,0" Width="494" CanUserAddRows="False" Background="#FF20E0BB" AlternatingRowBackground="#FF7CB4AB" Height="405" VerticalAlignment="Top" Grid.RowSpan="10">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Emp_id}" Header="Employee ID"></DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding FirstName}" Header="FirstName"></DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding LastName}" Header="LastName"></DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding Age}" Header="Age"></DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding ZipCode}" Header="ZipCode"></DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding PhoneNumber}" Header="PhoneNumber"></DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding MobileNumber}" Header="MobileNumber"></DataGridTextColumn>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button Content="Edit" Command="{Binding DataContext.SelectEmployeeCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding SelectedEmployee}"></Button>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>

        <Label Content="*" Grid.Row="1" Height="30" HorizontalAlignment="Left" Margin="152,10,0,0" Name="lblReqField" VerticalAlignment="Top" Width="16" Foreground="Red" Grid.RowSpan="2" />
        <Label Name="lblPersonalInfo" Content="PERSONAL INFO" FontFamily="Calibri" FontSize="20" Foreground="#FF1C48D8" Grid.Column="1" Grid.Row="1" Height="36" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.ColumnSpan="2" Width="270" />
        <Label Grid.Column="1" Grid.Row="2" Height="35" HorizontalAlignment="Left" Name="lblFirstName" Content="FirstName" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="80" />
        <TextBox Grid.Column="2" Grid.Row="2" Height="25" HorizontalAlignment="Left" Name="txtFirstName" Text="{Binding employee.FirstName, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Window}},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" BorderBrush="#FF179EB7" VerticalAlignment="Top" Width="130" Margin="9,4,0,0" />


        <Label Height="35" HorizontalAlignment="Left" Margin="0,9,0,0" Name="lblLastName" Content="LastName" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="80" Grid.Column="1" Grid.Row="3" />
        <TextBox BorderBrush="#FF179EB7" Height="25" HorizontalAlignment="Left" Name="txtLastName" Text="{Binding Path=DataContext.LastName, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid}},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="130" Grid.Column="2" Grid.Row="3" Margin="8,9,0,0" />

        <Label Height="35" HorizontalAlignment="Left" Margin="0,10,0,0" Name="lblDob" Content="DOB" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="56" Grid.Column="1" Grid.Row="4" />
        <DatePicker Grid.Column="2" Grid.Row="4" Name="dtpDob" Text="{Binding DataContext.Dob, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid}},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Height="25" HorizontalAlignment="Left" Margin="9,10,0,0" VerticalAlignment="Top" Width="129" />

        <Label Height="28" HorizontalAlignment="Left" Name="lblAge" Content="Age" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="56" Grid.Column="1" Grid.Row="5" Margin="0,6,0,0" />
        <TextBox Height="25" HorizontalAlignment="Left" Name="txtAge" BorderBrush="#FF179EB7" IsReadOnly="True" Text="{Binding DataContext.Age, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid}},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="129" Grid.Column="2" Grid.Row="5" Margin="9,6,0,0" />


        <Label Height="35" HorizontalAlignment="Left" Margin="0,6,0,0" Name="lblAddressInfo" Content="ADDRESS INFO" FontFamily="Calibri" FontSize="20" VerticalAlignment="Top" Width="270" Grid.Column="1" Grid.Row="6" Grid.ColumnSpan="2" />


        <Label Height="35" HorizontalAlignment="Left" Name="lblStreet1" Content="Street1" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="56" Grid.Column="1" Grid.Row="7" />
        <TextBox Name="txtStreet1" BorderBrush="#FF179EB7" Text="{Binding DataContext.Street1, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid}},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Height="25" HorizontalAlignment="Left" VerticalAlignment="Top" Width="129" Grid.Column="2" Grid.Row="7" Margin="9,8,0,0" />

        <Label Height="35" HorizontalAlignment="Left" Name="lblStreet2" Content="Street2" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="56" Grid.Column="1" Grid.Row="8" />
        <TextBox Name="txtStreet2" BorderBrush="#FF179EB7" Text="{Binding Path=Street2, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid}},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Height="25" HorizontalAlignment="Left" Margin="9,6,0,0" VerticalAlignment="Top" Width="130" Grid.Column="2" Grid.Row="8" />

        <Label Height="35" HorizontalAlignment="Left" Name="lblCity" Content="City" FontFamily="Calibri" FontSize="16" Margin="0,1,0,0" VerticalAlignment="Top" Width="56" Grid.Column="1" Grid.Row="9" />
        <ComboBox Text="{Binding DataContext.City, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid}},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Name="cmbCity" Width="129" Height="25" BorderBrush="#FF179EB7" Grid.Column="2" Grid.Row="9" HorizontalAlignment="Left" Margin="9,5,0,0" VerticalAlignment="Top">
            <ComboBoxItem Content="Kasargod"></ComboBoxItem>
            <ComboBoxItem Content="Kannur"></ComboBoxItem>
            <ComboBoxItem Content="Thrissur"></ComboBoxItem>
            <ComboBoxItem Content="Ernakulam"></ComboBoxItem>
            <ComboBoxItem Content="Palakkad"></ComboBoxItem>
            <ComboBoxItem Content="Alappuzha"></ComboBoxItem>
            <ComboBoxItem Content="Chennai"></ComboBoxItem>
            <ComboBoxItem Content="WhiteField"></ComboBoxItem>
            <ComboBoxItem Content="Bangalore"></ComboBoxItem>
        </ComboBox>

        <Label Height="35" HorizontalAlignment="Left" Name="lblState" Content="State" FontFamily="Calibri" FontSize="16" Margin="0,4,0,0" VerticalAlignment="Top" Width="56" Grid.Column="1" Grid.Row="10" />
        <ComboBox Height="25" HorizontalAlignment="Left" Margin="9,6,0,0" VerticalAlignment="Top" Width="129" Grid.Column="2" Grid.Row="10" Text="{Binding Path=State, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Name="cmbState" BorderBrush="#FF179EB7">
            <ComboBoxItem Content="Kerala"></ComboBoxItem>
            <ComboBoxItem Content="Karnataka"></ComboBoxItem>
            <ComboBoxItem Content="TamilNadu"></ComboBoxItem>
            <ComboBoxItem Content="AndhraPradesh"></ComboBoxItem>
            <ComboBoxItem Content="Rajastan"></ComboBoxItem>
        </ComboBox>


        <Label Height="35" HorizontalAlignment="Left" Name="lblZipcode" Content="ZipCode" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="65" Grid.Column="1" Grid.Row="11" Margin="3,5,0,0" />
        <TextBox Name="txtZipcode" BorderBrush="#FF179EB7" Text="{Binding Path=ZipCode,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Height="25" HorizontalAlignment="Left" VerticalAlignment="Top" Width="130" Grid.Column="2" Grid.Row="11" Margin="9,0,0,0" />

        <Label Height="35" HorizontalAlignment="Left" Name="lblContactInfo" Content="CONTACT INFO" FontFamily="Calibri" FontSize="20" VerticalAlignment="Top" Width="165" Grid.Column="3" Grid.Row="6" Grid.ColumnSpan="2" Margin="0,6,0,0" />


        <Label Height="35" HorizontalAlignment="Left" Name="lblPhoneNumber" Content="PhoneNumber" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="110" Grid.Column="3" Grid.Row="7" />
        <TextBox Height="25" Name="txtPhoneNo" BorderBrush="#FF179EB7" Text="{Binding Path=PhoneNumber,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" HorizontalAlignment="Left" Margin="1,0,0,0" VerticalAlignment="Top" Width="129" Grid.Column="4" Grid.Row="7" />



        <Label Height="35" HorizontalAlignment="Left" Name="lblMobileNumber" Content="MobileNumber" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="109" Grid.Column="3" Grid.Row="8" Margin="1,2,0,0" Grid.RowSpan="2" />
        <TextBox Name="txtMobileNo" BorderBrush="#FF179EB7" Text="{Binding Path=MobileNumber,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Height="25" HorizontalAlignment="Left" Margin="1,4,0,0" VerticalAlignment="Top" Width="129" Grid.Column="4" Grid.Row="8" />

        <Label Height="35" HorizontalAlignment="Left" Name="lblEmail" Content="Email" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="56" Grid.Column="3" Grid.Row="9" Margin="0,1,0,0" />
        <TextBox Name="txtEmail" BorderBrush="#FF179EB7" Text="{Binding Path=Email,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Height="25" HorizontalAlignment="Left" Margin="0,5,0,0" VerticalAlignment="Top" Width="129" Grid.Column="4" Grid.Row="9" />

        <Label Height="35" HorizontalAlignment="Left" Name="lblWeb" Content="Web" FontFamily="Calibri" FontSize="16" VerticalAlignment="Top" Width="56" Grid.Column="3" Grid.Row="10" Margin="1,4,0,0" />
        <TextBox Name="txtWeb" BorderBrush="#FF179EB7" Text="{Binding Path=Web,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Height="25" HorizontalAlignment="Left" Margin="1,4,0,0" VerticalAlignment="Top" Width="129" Grid.Column="4" Grid.Row="10" />
    </Grid>


</Window>

I don't see what exactly you are attempting to bind to, but reguardless, I also don't see an implementation of the INotfyPropertyChanged interface on your class. You should implement this in your ViewModel like so:

public class SomeClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    // This method is called by the Set accessor of each property. 
    // The CallerMemberName attribute that is applied to the optional propertyName 
    // parameter causes the property name of the caller to be substituted as an argument. 
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    private int percent = 0;
    public int Percent 
    {
        get { return percent; }
        set
        {
            if (value != percent)
            {
                percent = value;
                NotifyPropertyChanged();
            }
        }
    }
}

The binding here would then be straight forward

...
<ProgressBar Value="{Binding Percent}" />
...

I hope this helps.

Note that you need .NET-Framework 4.5 our above to use CallerMemberName .

The problem is that you have 2 EmployeeViewModel objects. The one is created in the XAML and assigned to the DataContext and the one created when selectEmployeeDetails() is called.

It's unclear as to why you are creating another EmployeeViewModel object within selectEmployeeDetails() which presumably is a member function of EmployeeViewModel.

Should you just set the current objects properties within this function?

By the way,

public string Emp_ID;
public string emp_ID
    {
        get
        {
            return emp_ID;
        }
        set
        {
            emp_ID = value;
            OnPropertyChanged("Emp_ID");
        }
    }

this will cause recursive calling and setting, alter emp_ID to Emp_ID

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