0

All -

I am trying to set public property in VM based on the current item in Observable collection (which is also in VM). So essentially - I want to set shadecolor as Blue or Pink based on the row I am (see sample code below). Also see image of what the end result will look like.

Can somebody please suggest - how I can achieve this - am really stuck with this problem

See sample code below:

Model.cs

public class Model
{
    public Employee empdetails { get; set; }
}

public class Employee
{
    public string fname { get; set; }
    public string lname { get; set; }
    public Enum gender { get; set; }
}

public enum gender
{
    Male,
    Female
}

ViewModel.cs

public class ViewModel
{
    public ObservableCollection<Model> employees {get; set;}
    public myCommand NextCommand { get; set; }
    private Color _shadecolor;

    public Color shadecolor
    {
        get
        {
            return _shadecolor;
        }
        set
        {
            _shadecolor = value;
        }
    }

    public ViewModel()
    {
        employees = new ObservableCollection<Model>()
        {
            #region Populating Emp 1
            new Model()
            {
                empdetails = new Employee()
                {
                    fname = "John",
                    lname = "Smith",
                    gender = gender.Male
                }
            },
            #endregion

            #region Populating Emp 2
            new Model()
            {
                empdetails = new Employee()
                {
                    fname = "Robert",
                    lname = "Ally",
                    gender = gender.Female
                }
            },
            #endregion
        };

        NextCommand = new myCommand(myNextCommandExecute, myCanNextCommandExecute);
    }

    private void myNextCommandExecute(object parameter)
    {

    }

    private bool myCanNextCommandExecute(object parameter)
    {
        return true;
    }
}

View.xaml

<Window x:Class="WpfApplication1.View"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="View" Height="500" Width="500"  WindowStyle="None" AllowsTransparency="True" Background="Transparent">
<Border VerticalAlignment="Top" HorizontalAlignment="Left" BorderBrush="Silver" BorderThickness="2" CornerRadius="15">
    <Border.Background>
        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.511,0.957">
            <GradientStop Color="LightGray" Offset="0.55" />
            <GradientStop Color="{Binding shadecolor}" Offset="1.3" />
        </LinearGradientBrush>
    </Border.Background>
    <Grid Width="300" Height="300" Margin="3">
        <StackPanel VerticalAlignment="Top" >
            <TextBlock Text="{Binding Path=employees/empdetails.fname}" />
            <Button Command="{Binding NextCommand}" Content="Next" Width="100"></Button>
        </StackPanel>
    </Grid>
</Border>
</Window>

enter image description here

Kevin DiTraglia
  • 25,746
  • 19
  • 92
  • 138
Patrick
  • 864
  • 1
  • 14
  • 27

3 Answers3

1

I believe what you want is to bind SelectedItem={Binding SelectedItem} where Selected item is on the view model as well, exposed as an observable property.

public Model SelectedItem
{
   ...
}

I'm not totally sure what you are trying to achieve here though as you don't have anything in your XAML deriving from Selector, therefore there is no concept of a selected item here.

Firoso
  • 6,647
  • 10
  • 45
  • 91
  • Firoso - can you please put forth some more sample code. I see what you are trying to say above but not entirely sure how to integrate this in the above sample code. It has to be an observable property - and that part I totally understand (with implementation of OnPropertyChanged). Looking forward more to how to set this property from the existing observable collection – Patrick Oct 08 '12 at 22:35
  • What is your scenario of setting it? does the binding update when the user selects a new item? does it work when the view model changes? both? – Firoso Oct 08 '12 at 22:57
  • the only way the binding updates is when user clicks on the next button and now it goes to the second item of the observable collection. The observable collection itself is only updated with new entries from the backend data access system through model (not from the user) – Patrick Oct 08 '12 at 23:14
  • Updated, in short, I'm not sure I understand what you are trying to achieve from a high level. Is there a behavior you are implementing or emulating? – Firoso Oct 08 '12 at 23:27
  • Essentially the record being displayed to the user (for example john here) is the selected item. So essentially I want to have everything associated with John (his full employee object) available in a property within ViewModel - so I can do futher logic and have it displayed in the View. Does that make sense - if not - can we have this quick conversation in Chat and I can walk through the logic behind this. – Patrick Oct 08 '12 at 23:38
  • Then an observable collection is not needed as you are not binding to the collection, have a property of type Model, and then have the viewmodel update it when a command is called by analyzing the collection. Your DataContext is going to have to be bound to the Model property. – Firoso Oct 08 '12 at 23:49
  • Firoso - Was able to get it working based on your few suggestions. Thanks for all your pointers which helped me visualize in this direction. Though I would also like to explore the approach above which Josh has mentioned but am unclear on the implementation at this point – Patrick Oct 09 '12 at 05:25
1

Why not use a ValueConverter?

<GradientStop Color="{Binding Path=gender, Converter={StaticResource GenderToColorConverter}" Offset="1.3" />

Then inside your value converter:

If value == Gender.Male return blue; 
return pink;

Technically, I think you return a Brush, but don't quote me on that.

Here's some sample code:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = new ViewModel(GetTestEmployees());
        }

        static IEnumerable<Employee> GetTestEmployees()
        {
            return new[]
            {
                new Employee()
                {
                    FirstName = "Tom",
                    LastName = "Selleck",
                    Gender = Gender.Male
                },
                new Employee()
                {
                    FirstName = "Pat",
                    LastName = "Sajak",
                    Gender = Gender.Male,
                },
                new Employee()
                {
                    FirstName = "Mae",
                    LastName = "West",
                    Gender = Gender.Female
                }
            };
        }
    }

    public class ViewModel : INotifyPropertyChanged
    {
        public ViewModel(IEnumerable<Employee> employees)
        {
            _employees = new ObservableCollection<Employee>(employees);
            SelectedEmployee = employees.First();
        }

        ObservableCollection<Employee> _employees;
        public ObservableCollection<Employee> Employees
        {
            get { return _employees; }
        }

        Employee _selectedEmployee;
        public Employee SelectedEmployee 
        {
            get { return _selectedEmployee; }
            set
            {
                _selectedEmployee = value;
                RaisePropertyChangedEvent("SelectedEmployee");
            }
        }

        public void Next()
        {
            var curr = Employees.IndexOf(_selectedEmployee);

            if (curr == -1) throw new ArgumentOutOfRangeException();

            var next  = (curr + 1) % Employees.Count;

            SelectedEmployee = Employees[next];
        }

        ICommand _nextCommand;
        public ICommand NextCommand
        {
            get
            {
                if (_nextCommand == null)
                    _nextCommand = new NextCommand(this);

                return _nextCommand;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChangedEvent(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class NextCommand : ICommand
    {
        ViewModel _viewModel;

        public NextCommand(ViewModel viewModel)
        {
            _viewModel = viewModel;
        }

        public bool CanExecute(object parameter)
        {
            //throw new NotImplementedException();

            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            //throw new NotImplementedException();

            _viewModel.Next();
        }
    }

    public class Employee
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Gender Gender { get; set; }
    }

    public enum Gender
    {
        Male,
        Female
    }

    public class GenderToColorConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var gender = (Gender)value;

            if (gender == Gender.Male)
            {
                return Colors.Blue;
            }

            return Colors.Pink;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();


}
}

And here's the corresponding markup:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:loc="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <loc:GenderToColorConverter x:Key="GenderToColorConverter"/>
    </Window.Resources>
    <Grid>
        <ListBox ItemsSource="{Binding Employees}"
                      SelectedItem="{Binding SelectedEmployee}">
            <ListBox.Template>
                <ControlTemplate TargetType="ListBox">
                    <Grid>
                        <ContentControl DataContext="{TemplateBinding SelectedItem}">
                            <StackPanel >
                                <StackPanel.Background>
                                    <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.511,0.957">
                                        <GradientStop Color="LightGray" Offset="0.55" />
                                        <GradientStop Color="{Binding Path=Gender, Converter={StaticResource GenderToColorConverter}}" Offset="1.3" />
                                    </LinearGradientBrush>
                                </StackPanel.Background>
                                <TextBox Text="{Binding FirstName}"/>
                                <TextBox Text="{Binding LastName}"/>
                            </StackPanel>
                        </ContentControl>
                    </Grid>
                </ControlTemplate>
            </ListBox.Template>

        </ListBox>

        <Button VerticalAlignment="Bottom" HorizontalAlignment="Center" Content="Next" Command="{Binding NextCommand}"/>
    </Grid>
</Window>
Josh C.
  • 4,303
  • 5
  • 30
  • 51
  • Josh - I wasnt taking that approach because if I have public property which contains the complete selected item (from the observable collection) - I can leverage multiple things in that to set different things in the View. The Border shade was part of the thing (as this was the sample to demo the problem). The key part i am trying to get to is how to populate a SelectedItem property in VM (which can contain the full employee object) based on the current item in the Observable Collection – Patrick Oct 08 '12 at 23:30
  • @Patrick I see. I still stand by my solution. However, you should be using an ItemsControl for your main view. Your ItemsControl.ItemsSource should be bound to the collection in your ViewModel. You should also bind the SelectedItem property to your ViewModel. You should create a Command in your ViewModel for your Next button. You should template your ItemsControl to get the view you want. You should bind your brush to the gender property. In general, putting presentation information (colors, styles, etc) in your ViewModel is bad design. – Josh C. Oct 09 '12 at 02:28
  • Josh - as you see below I was able to get to my goal by using Firoso's approach - but am really also keen to learn your approach. That was my original line of thinking too but cant get to a working solution. Infact before posting this issue - I was originally binding to Listbox and setting my Itemssource to employees (and had that working) but then came across this post - http://stackoverflow.com/questions/6115936/bind-to-a-listbox-but-only-show-the-selected-element. Is it possible - you can briefly edit your answer and show me the VM/View implementation of what you are suggesting. – Patrick Oct 09 '12 at 05:37
  • @Patrick I've added some sample code. I definitely wouldn't do it exactly this way, but this should get you going. – Josh C. Oct 09 '12 at 18:26
0

I was able to implement this (fully working) solution using the approach Firoso has mentioned (leveraging the best practice too by Josh of keeping UI logic out of VM).

Posting the full code snippet/image for benefit of others.

Model

public class Model : CommonBase
{
    public Employee empdetails { get; set; }
}

public class Employee : CommonBase
{
    private string _fname;
    public string fname 
    {
        get
        {
            return _fname;
        }

        set
        {
            _fname = value;
            OnPropertyChanged("fname");
        }
    }
    public string lname { get; set; }
    private Enum _gender;
    public Enum gender
    {
        get
        {
            return _gender;
        }
        set
        {
            _gender = value;
            OnPropertyChanged("gender");
        }
    }
}

public enum gender
{
    Male,
    Female
}

ViewModel

public class ViewModel
{
    public Model employees { get; set; }
    public myCommand NextCommand { get; set; }

    public ViewModel()
    {
        employees = new Model()
        {
            empdetails = new Employee()
            {
                fname = "John",
                lname = "Doe",
                gender = gender.Male
            }
        };

        NextCommand = new myCommand(myNextCommandExecute, myCanNextCommandExecute);
    }

    private void myNextCommandExecute(object parameter)
    {
        employees.empdetails.fname = "Ally";
        employees.empdetails.lname = "Smith";
        employees.empdetails.gender = gender.Female;
    }

    private bool myCanNextCommandExecute(object parameter)
    {
        return true;
    }
}

View

<Window x:Class="WpfApplication1.View"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:loc="clr-namespace:WpfApplication1"
    Title="View" Height="500" Width="500"  WindowStyle="None" AllowsTransparency="True" Background="Transparent">
<Window.Resources>
    <loc:GendertoColorConverter x:Key="GendertoColorConverter"/>
</Window.Resources>
<Border VerticalAlignment="Top" HorizontalAlignment="Left" BorderBrush="Silver" BorderThickness="2" CornerRadius="15">
    <Border.Background>
        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.511,0.957">
            <GradientStop Color="LightGray" Offset="0.55" />
            <GradientStop Color="{Binding Path=employees.empdetails.gender, Converter={StaticResource GendertoColorConverter}}" Offset="1.3" />
        </LinearGradientBrush>
    </Border.Background>
    <Grid Width="300" Height="300" Margin="3">
        <StackPanel VerticalAlignment="Top" >
            <TextBlock Text="{Binding Path=employees.empdetails.fname}" />
            <Button Command="{Binding NextCommand}" Content="Next" Width="100"></Button>
        </StackPanel>
    </Grid>
</Border>

enter image description here

Patrick
  • 864
  • 1
  • 14
  • 27
  • You really should use an ItemsControl and bind the ItemsSource and SelectedItem properties to your VM. – Josh C. Oct 09 '12 at 13:25
  • I've added some sample code to my solution. I definitely wouldn't do it exactly this way, but this should get you going. – Josh C. Oct 09 '12 at 18:26