15

I would like to bind the foreground property of a TextBlock to a Property in my ViewModel.

This doesn't work :

Edit

View :

TextBlock 
Text="{Binding Path=FullName, Mode=OneWay}" 
Foreground="{Binding Path=ForegroundColor}"
Margin="0 5 3 5"

Code behind:

CustomerHeaderViewModel customerHeaderViewModel = new CustomerHeaderViewModel();
customerHeaderViewModel.LoadCustomers();
CustomerHeaderView.DataContext = customerHeaderViewModel;

View Model:

private System.Windows.Media.Brush _foregroundColor;
_foregroundColor = System.Windows.Media.Brushes.DarkSeaGreen;

public System.Windows.Media.Brush ForegroundColor
{
    get { return _foregroundColor; }
    set { _foregroundColor = value; 
        OnPropertyChanged("ForegroundColor");
    }
}

public CustomerHeaderViewModel()
{
ForegroundColor = System.Windows.Media.Brushes.Red;
}

All other properties (Text etc) correctly bind.

What am I doing wrong?

Breeze
  • 2,010
  • 2
  • 32
  • 43
Joe.Net
  • 1,175
  • 5
  • 19
  • 36

2 Answers2

27

Check if your solution is like that: View:

<Window x:Class="WpfApplication13.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:vm="clr-namespace:WpfApplication13"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <vm:MainVM/>
    </Window.DataContext>
    <Grid>
        <TextBlock Text="{Binding Path=FullName, Mode=OneWay}" 
                   Foreground="{Binding Path=ForegroundColor}"
                   Margin="0 5 3 5"/>
    </Grid>
</Window>

ViewModel:

public class MainVM : INotifyPropertyChanged
{
    protected void OnPropertyChanged(string porpName)
    {
        var temp = PropertyChanged;
        if (temp != null)
            temp(this, new PropertyChangedEventArgs(porpName));
    }
    public event PropertyChangedEventHandler PropertyChanged;

    private System.Windows.Media.Brush _foregroundColor = System.Windows.Media.Brushes.DarkSeaGreen;

    public string FullName
    {
        get
        {
            return "Hello world";
        }
    }

    public System.Windows.Media.Brush ForegroundColor
    {
        get { return _foregroundColor; }
        set
        {
            _foregroundColor = value;
            OnPropertyChanged("ForegroundColor");
        }
    }
}

Running app

and remember that if you want to set new value for ForegroundColor in VM you sholud do it like that:

ForegroundColor = System.Windows.Media.Brushes.Red;

to raise PropertyChangedEvent

Accordind to new information about your problem, you could try this solution:

CustomerHeaderViewModel.cs

class CustomerHeaderViewModel : INotifyPropertyChanged
{
    public ObservableCollection<Customer> Customers { get; set; }

    public void LoadCustomers()
    {
        ObservableCollection<Customer> customers = new ObservableCollection<Customer>();

        //this is where you would actually call your service
        customers.Add(new Customer { FirstName = "Jim", LastName = "Smith", NumberOfContracts = 23 });
        customers.Add(new Customer { FirstName = "Jane", LastName = "Smith", NumberOfContracts = 22 });
        customers.Add(new Customer { FirstName = "John", LastName = "Tester", NumberOfContracts = 33 });
        customers.Add(new Customer { FirstName = "Robert", LastName = "Smith", NumberOfContracts = 2 });
        customers.Add(new Customer { FirstName = "Hank", LastName = "Jobs", NumberOfContracts = 5 });

        Customers = customers;
    }
    protected void OnPropertyChanged(string porpName)
    {
        var temp = PropertyChanged;
        if (temp != null)
            temp(this, new PropertyChangedEventArgs(porpName));
    }
    public event PropertyChangedEventHandler PropertyChanged;

    private System.Windows.Media.Brush _foregroundColor = System.Windows.Media.Brushes.DarkSeaGreen;

    public System.Windows.Media.Brush ForegroundColor
    {
        get { return _foregroundColor; }
        set
        {
            _foregroundColor = value;
            OnPropertyChanged("ForegroundColor");
        }
    }
}

CustomerHeaderView.xaml

<UserControl x:Class="TestMvvm444.Views.CustomerHeaderView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="main">
    <Grid>
        <StackPanel HorizontalAlignment="Left">
            <ItemsControl ItemsSource="{Binding Path=Customers}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <StackPanel Orientation="Horizontal">
                                <TextBox
                                Text="{Binding Path=FirstName, Mode=TwoWay}" 
                                Width="100" 
                                Margin="3 5 3 5"/>
                                <TextBox 
                                Text="{Binding Path=LastName, Mode=TwoWay}" 
                                Width="100"
                                Margin="0 5 3 5"/>
                                <TextBlock 
                                Text="{Binding Path=FullName, Mode=OneWay}" 
                                Foreground="{Binding ElementName=main, Path=DataContext.ForegroundColor}"
                                Margin="0 5 3 5"/>
                            </StackPanel>
                        </StackPanel>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </StackPanel>
    </Grid>
</UserControl>

In presented scenario the ForegroundColor property resides in CustomerHeaderViewModel.cs so it is value for all customers. In CustomerHeaderView.xaml I added x:Name for UserControl to have a possiblity to refer to DataContext of this element. If you don't want to use x:Name for UserControl, you can try this:

<TextBlock 
    Text="{Binding Path=FullName, Mode=OneWay}"
    Foreground="{Binding RelativeSource={RelativeSource FindAncestor,
    AncestorType={x:Type UserControl}}, Path=DataContext.ForegroundColor}"
    Margin="0 5 3 5"/>

Remember that DataContext of this control was set earlier in MainWindow.cs.

MainWindow.cs

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        CustomerHeaderViewModel customerHeaderViewModel = new CustomerHeaderViewModel();
        customerHeaderViewModel.LoadCustomers();
        CustomerHeaderView.DataContext = customerHeaderViewModel;
    }
}

App

PawelSt
  • 826
  • 1
  • 10
  • 6
  • Thanks very much PawelSt, still getting the same problem..I've added further code, above. The text block sits inside a user control, but as mentioned all Properties are loaded correctly - just not the ForegroundColor.. Thanks – Joe.Net Apr 09 '11 at 08:01
  • I don't uderstand exactly your scenario, and what could went wrong, but try to change a type of ForegroundColor from Brush to SolidColorBrush. – PawelSt Apr 09 '11 at 08:20
  • Thanks PawelSt, the Foreground Binding was not working on my app, so I went back to basics, downloaded and amended Edward Tanguay's example : http://www.tanguay.info/web/index.php?pg=codeExamples&id=139 to change the Foreground property of a TextBlock, hoping to fix my problem there and transfer to the solution to my app.. – Joe.Net Apr 09 '11 at 10:02
  • I improved my answer, so check if this new solution works for you. – PawelSt Apr 09 '11 at 14:16
  • Thanks very much PawelSt, this is a great solution - many Thanks. Joe – Joe.Net Apr 09 '11 at 15:42
4

This is not a good practice to put UI elements in your view model. Your view model must only encapsulate business locig.

If you want to change the color of anything in your UI that depends on on the value of your textbox, it's a better practice to use data triggers in XAML.

You can do like this :

Viewmodel :

public class MainVm : INotifyPropertyChanged
{
    protected void OnPropertyChanged(string porpName)
    {
        var temp = PropertyChanged;
        if (temp != null)
            temp(this, new PropertyChangedEventArgs(porpName));
    }
    public event PropertyChangedEventHandler PropertyChanged;

    public string FullName
    {
        get { return "Hello world"; }
    }
}

XAML (Edited to use the color picker, assuming the selected value of his control is named "SelectedValue" and that it returns a Brush object)

    <Grid>
    <TextBlock Text="{Binding Path=FullName, Mode=OneWay}" 
               Margin="0 5 3 5" Foreground="{Binding ElementName=colorpicker, Path=SelectedValue}"/>
    <ColorPicker x:Name="colorpicker"/>
</Grid>
Bruno
  • 1,944
  • 13
  • 22
  • Thanks Bruno - I am using a Color Picker control to set the ForegroundColor in the view model as it could be any valid Hex number - hence why I need the logic in the view model, and I don't think binding in the Setter is supported.. – Joe.Net Apr 09 '11 at 09:40
  • If you can bind the selected value of your color picker to your view model, then you can bind directly the Foreground property of your text box directly to you color picker :) I have edited my response to give you an example. Nothing simpler, and no need to use your viewmodel anymore! Thus, each time the user changes the value in color picker, it must automatically update your textbox foreground, all in XAML binding :) – Bruno Apr 09 '11 at 11:56
  • Thanks for your time and suggestions Bruno. I have several user controls in my app and their viewmodels are updated through subscribing to an event aggregator event, so 'Binding ElementName=colorpicker' would not work.... thats why i went back to basics just to solve this problem, by dowloading and amending Edward Tanguay's example : tanguay.info/web/index.php?pg=codeExamples&id=139 to change the Foreground property of a TextBlock, hoping to fix my problem there and transfer to the solution to my app – Joe.Net Apr 09 '11 at 12:38