Im' a huge XAML noob, and I'm building an WinRT application with MVVM. Basically, I have a function getBackgroundColor() that returns a SolidColorBrush, and I want to make it so that at any given time, I can change the background color, and have the XAML element notice and change to match the new output of getBackgroundColor. I'm aware that this should be done with bindings but don't really understand any of the documentation online. Can someone explain this to me like I'm 5?
问题:
回答1:
Flip's answer is a textbook answer and will most likely achieve the results you're looking for. Often for me, I find MVVM to be a constant practice of academia vs real world. Following the MVVM pattern works in your scenario, but there are other ways to change the UI color. And this is where the conversation begins. Does the ViewModel need to know about how the view is displaying whatever it is you're displaying? If yes, stick to the other answer. If no, slap it in the view (your window.xaml and window.xaml.cs)
For instance, I have a color converter I use, because i decided that my ViewModel doesn't need to know what color I'm switching between in the view. So in the view xaml.cs (or really you can declare this anywhere since it's a class) I defined:
/// <summary>
/// A way to convert the color of text based upon maintenance required
/// </summary>
public class MaintenenceColorConverter : IValueConverter
{
#region Properties
// Properties
public Color NormalColor { get; set; }
public Color NoMaintenanceRequiredColor { get; set; }
#endregion
/// <summary>
/// DEFAULT CONSTRUCTOR
/// </summary>
public MaintenenceColorConverter() { }
/// <summary>
/// Convert the color of the text based upon maintenance required
/// </summary>
/// <returns>
/// The appropriate color property
/// </returns>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value.ToString() == "No Maintenance Required") return NoMaintenanceRequiredColor.ToString();
return NormalColor.ToString();
}
/// <summary>
/// Not used: NECESSARY FOR IMPLEMENTATION
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
}
}
Now this isn't pretty because I feel like I kind of violate the MVVM design pattern by inspecting in the view the viewmodel's data, but I'm okay with it (let the flame begin!).
So at this point all you need to do then in your xaml is to define your color converter:
<UserControl.Resources>
<localColor:MaintenenceColorConverter x:Key="MyColorConverter" NormalColor="Black" NoMaintenanceRequiredColor="Gray" />
</UserControl.Resources>
And then use it wherever necessary! I used it in a datagrid to "gray" out selections:
<DataGrid.Columns>
<DataGridTextColumn Header="Site Number" Binding="{Binding Path=SiteNo}" IsReadOnly="True" Width="100">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{Binding Path=MaintStatus, Converter={StaticResource MyColorConverter}}" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGrid.Columns>
Cheers!
回答2:
Assuming you know what a property and interface are - you need a property to bind to, so for example you would have a Border
element with a binding set up like Background={Binding BackgroundBrush}
. Then you would have a view model object of a type that implements the INotifyPropertyChanged
interface. The interface has one event - PropertyChanged
and you need to raise the event when a value of a property changes, so most typically the "bindable" property will raise the event in its setter when it's being set to a value different than the previous value. Your view model code might look like this then:
public event PropertyChangedEventHandler PropertyChanged;
private Brush _backgroundBrush;
public Brush BackgroundBrush
{
get
{
return Brush _backgroundBrush;
}
set
{
if (_backgroundBrush == value)
return;
_backgroundBrush = value;
PropertyChanged(this, new PropertyCHangedEventArgs("BackgroundBrush");
}
}
More frequently though you would create a base class somewhat like this:
abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (object.Equals(storage, value))
{
return false;
}
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
}
Then your view model could look like this:
class MyViewModel : BindableBase
{
private Brush _backgroundBrush;
public Brush BackgroundBrush
{
get { return Brush _backgroundBrush; }
set { SetProperty(ref _backgroundBrush, value); }
}
}
This makes your view models cleaner and safer for refactoring (you're not passing property names around).
As for modifying a XAML element's color while the view is onscreen - what's the point? If it's color is only modified when it's on screen - why not use the same color always? Also if you're writing any code that responds to changes in the UI like scrolling - it's typically better to write that code on the view side - in a control, attached behavior or a service class of some sort.