I've recently started development on a Windows 10 app which requires the display of events and extra information on a CalendarView. With the release of the new API, a new CalendarView component was also introduced, so I decided to give it a try. It's a nice widget, but customization has been a hell.
I've gotten to the point where I can display custom information using a ControlTemplate, but binding events and styling with VisualState has been quite a struggle.
This is the ControlTemplate I'm using wrapped in a Style.
<Style x:Key="dayItemStyle" TargetType="CalendarViewDayItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CalendarViewDayItem">
<Grid x:Name="DayItemEventListRoot">
<ListView ItemsSource="{TemplateBinding DataContext}" Padding="20,0,0,0" x:Name="EventInfoList"
IsItemClickEnabled="True" cm:Message.Attach="[Event ItemClick] = [ListTapped]">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="EventInfoPanel" Orientation="Horizontal">
<TextBlock x:Name="EventTime" Text="{Binding Date, Converter={StaticResource StringFormatter}, ConverterParameter=\{0:HH:mm\} }"
Foreground="{Binding Foreground, RelativeSource={RelativeSource Self}}" >
</TextBlock>
<TextBlock x:Name="EventDesc" Text="{Binding Name}" Padding="5,0,0,0" Foreground="Black" >
</TextBlock>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Normal" />
<VisualState x:Name="Today" >
<VisualState.Setters>
<Setter Target="EventTime.Foreground" Value="White" />
<Setter Target="EventDesc.Foreground" Value="White" />
<Setter Target="EventTime.Text" Value="DASFASDDF" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The style is then directly set on the CalenderView component.
<CalendarView Name="FlowCalendar" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalDayItemAlignment="Top" HorizontalDayItemAlignment="Left"
Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="5" Grid.RowSpan="4" CalendarViewDayItemChanging="FlowCalendar_CalendarViewDayItemChanging"
CalendarViewDayItemStyle="{StaticResource dayItemStyle}">
</CalendarView>
Extra information and states are controlled by the CalenderViewDayItemChanging event in the code-behind.
private void FlowCalendar_CalendarViewDayItemChanging(CalendarView sender, CalendarViewDayItemChangingEventArgs args)
{
if(args.Item.Date.Date.Equals(DateTime.Now.Date))
{
VisualStateManager.GoToState(args.Item, "Today", false);
// Testing, gives NullPointerException
//TextBlock bla = (TextBlock) args.Item.FindName("EventTime");
//bla.Text = "SDADASFASDF";
}
if (args.Phase == 0)
{
var eventsByDate = ViewModel.Upcoming.FirstOrDefault(eg => eg.Key.Date == args.Item.Date.Date);
if (eventsByDate != null)
{
args.Item.DataContext = eventsByDate.ToList();
}
}
}
Setting the visual state does nothing (I've checked if it's being called), moving the VisualState(Group) outside of the ControlTemplate just gives me an error that the targets could not be found.
I'm looking to control the event listview style through visual states, which for now are custom since I'm not sure what built-in states CalendarViewDayItem has. I'm quite new to visual states, so any pointers are much appreciated.
Thanks.
Your code will not work because VisualStateManager.GoToState takes a Control as the first parameter; however, your VisualStateManager is defined inside a StackPanel of which type is Panel.
You will need to implement your own VSM which takes a Panel instead. Have a look at the answer to this post on how to do this.
However, this still won't fix your problem. As you need to somehow locate the StackPanels (note 'cause it's within a ListView, there could be multiple) and then call the ExtendedVisualStateManager.GoToState.
I would suggest you to wrap this template within a UserControl (by doing this you might do not even need to extend the VSM as you can use GoToStateAction instead) and have a dependency property (IsToday) to control the states. Then you can use ElementName binding to pass a property at the CalendarViewDayItem level down to IsToday in order to make a state change.
Update
I was actually wrong about the need of using the ExtendedVisualManager to change the visual states. Since it's already inside a UserControl, you can actually call VisualStateManager.GoToState(this, "statename", flag); directly.
However, the way you define the dependency property is wrong.
Replace the code behind with the following and it should work.
public bool IsToday
{
get { return (bool)GetValue(IsTodayProperty); }
set { SetValue(IsTodayProperty, value); }
}
public static readonly DependencyProperty IsTodayProperty =
DependencyProperty.Register("IsToday", typeof(bool), typeof(EventListTemplate), new PropertyMetadata(false, OnIsTodayChanged));
static void OnIsTodayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var template = (EventListTemplate)d;
if ((bool)e.NewValue)
{
var stateset = VisualStateManager.GoToState(template, "DayItemToday", false);
Debug.WriteLine("did it:", stateset);
}
}
Related
I'm having an issue to style my custom control derived from button.
I inherited the Button class to add a DependencyProperty to it:
public class IconButton : Button, IStyleable
{
Type IStyleable.StyleKey => typeof(Button);
public static readonly StyledProperty<string> IconProperty =
AvaloniaProperty.Register<IconButton, string>(nameof(Icon));
public string Icon
{
get { return GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
}
Now I want to create a style targetting only this derived class:
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
xmlns:cc="*****.*****.CustomControls">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Orientation="Vertical">
<cc:IconButton Content="hello custom" Icon="fab fa-github"/>
<Button Content="hello"/>
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector=":is(cc|IconButton)">
<Setter Property="Background" Value="Red"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<i:Icon Value="{TemplateBinding Icon}" />
<TextBlock Text="{TemplateBinding Content}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Styles>
AS you can see, the content is not my control template as the icon is not visible and the background is not red. Therefore I can't use my dependencyproperty.
Any suggestions or is it not supported by the language?
I read on the documentation that is(****) for selector is intended to support derived type so I would expect my code to be working :(
Thx for help.
Try to remove IStyleable and your overriden StyleKey. You are telling the selector that it should look for a Button style and not your style.
Happy coding
Tim
PS I cannot test it on my own right now, so if it doesn't work please tell me here.
Update: I just made a blank new project and tested your code. Once I remove the StyleKey, it works:
This is the modified IconButton:
public class IconButton : Button
{
public static readonly StyledProperty<string> IconProperty =
AvaloniaProperty.Register<IconButton, string>(nameof(Icon));
public string Icon
{
get { return GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
}
If you are able to upload a minimal sample on Github, I can have a look what may be wrong.
Update 2: Looking into your demo App I see that you use a third party icon lib. The lib requires you to configure a special service at start up, see: https://github.com/Projektanker/Icons.Avalonia#1-register-icon-providers-on-app-start-up
I send you a PR which fixes your issue.
Happy coding
Tim
The WPF version of this question is here: But it hasn't been answered and I don't know if the UWP TreeView will have the same answer.
I'm trying to add a DataTemplateSelector to the new UWP TreeViews that were just added to windows 10 version 1803 but it isn't working. It is documented here how to use the XAML TreeView Control and even shows how to modify the template to change the Item Datatemplate which works fine. I need to use a datatemplate selector since each of my nodes is using different objects and I need them displayed differently. The TreeView.Node.Content is being set just fine and everything works except it is passing null over to the datatemplateselector in the Object parameter.
Here is my code: (same as the example from Microsoft just with using ItemTemplateSelector)
<Style TargetType="TreeView">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeView">
<TreeViewList x:Name="ListControl"
ItemTemplateSelector="{StaticResource CardSelector}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
CanDragItems="True"
AllowDrop="True"
CanReorderItems="True">
<TreeViewList.ItemContainerTransitions>
<TransitionCollection>
<ContentThemeTransition />
<ReorderThemeTransition />
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</TreeViewList.ItemContainerTransitions>
</TreeViewList>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Does anyone have any insight or experience on this? My datatemplateselector "CardSelector" works fine and I have been using it in several places without any trouble.
So the point of my question isn't to get anything that I have working but to see if the TreeViewControl works with a DataTemplateSelector. I only have "CardTemplateSelector" in there because I use it in several other places of my app and I know it works. My question is really a "yes, treeview works with a selector" or "no it doesn't" I'm really looking for someone else to try it with their own test template selector and to let me know if they can get it working. Any specific code from me is not relevant to the question. Just see if you can get it to work with whatever selector you want
Yes. The TreeView work well with ItemTemplateSelector.
I used the all code in the document and create a custom class like the following:
public class Test
{
public string Name { get; set; }
}
I made another DataTemplate like this:
<DataTemplate x:Key="TreeViewObjDataTemplate">
<Grid Height="44">
<TextBlock
Text="{Binding Content.Name}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Style="{ThemeResource BodyTextBlockStyle}"/>
</Grid>
</DataTemplate>
My CardTemplateSelector class is the following:
public class CardTemplateSelector: DataTemplateSelector
{
public DataTemplate TreeViewItemDataTemplate { get; set; }
public DataTemplate TreeViewObjDataTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
TreeViewNode treeViewNode = item as TreeViewNode;
if (treeViewNode.Content is StorageFolder|| treeViewNode.Content is StorageFile)
{
return TreeViewItemDataTemplate;
}
if (treeViewNode.Content is Test)
{
return TreeViewObjDataTemplate;
}
return base.SelectTemplateCore(item);
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return SelectTemplateCore(item);
}
}
I just add the new lines code in MainPage.xaml.cs:
TreeViewNode objnode = new TreeViewNode();
Test test = new Test() {Name="Parent"};
objnode.Content = test;
objnode.IsExpanded = true;
objnode.HasUnrealizedChildren = true;
sampleTreeView.RootNodes.Add(objnode);
The following is the whole xaml page resource code:
<Page.Resources>
<DataTemplate x:Key="TreeViewItemDataTemplate">
<Grid Height="44">
<TextBlock
Text="{Binding Content.DisplayName}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Style="{ThemeResource BodyTextBlockStyle}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TreeViewObjDataTemplate">
<Grid Height="44">
<TextBlock
Text="{Binding Content.Name}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Style="{ThemeResource BodyTextBlockStyle}"/>
</Grid>
</DataTemplate>
<local:CardTemplateSelector x:Name="CardTemplateSelector" TreeViewItemDataTemplate="{StaticResource TreeViewItemDataTemplate}" TreeViewObjDataTemplate="{StaticResource TreeViewObjDataTemplate}"></local:CardTemplateSelector>
<Style TargetType="TreeView">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeView">
<TreeViewList x:Name="ListControl"
ItemTemplateSelector="{StaticResource CardTemplateSelector}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
CanDragItems="True"
AllowDrop="True"
CanReorderItems="True">
<TreeViewList.ItemContainerTransitions>
<TransitionCollection>
<ContentThemeTransition />
<ReorderThemeTransition />
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</TreeViewList.ItemContainerTransitions>
</TreeViewList>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
So far I have answered your question. But I still want to let you know How to ask a good question. In my above comments, I asked you to provide the relevant code, then I could quickly reproduce your question and help you diagnose it. But you said I'm really looking for someone else to try it with their own test template selector and to let me know if they can get it working.. It's Ok. You could see that only I replied. You asked this question for many days. No other community members helped you on this question. That's why I ask you to post some code here. If you provide the relevant code here, I believe many community members will be glad to help you on this question. I really hope you could understand it.
There seems to be confusion about where to apply the DataTemplate. And the all important TargetType is ignored.
If you want a handle on the data item in your custom DataTemplateSelector, you need to:
OPTION 1
Apply the DataTemplateSelector on TreeView.ItemTemplateSelector
Make sure that the DataTemplates have TreeViewNode as the target type.
Only then the data item of the TreeViewNode is supplied to the SetTemplateCore(object item) and SetTemplateCore(object item, DependencyObject container) overrides of your custom DataTemplateSelector.
A working example is found here: Pictures and Music library tree view
OPTION 2
Apply the DataTemplateSelector on TreeViewItem.ContentTemplateSelector
Make sure that the DataTemplates have [YOUR-DATA-TYPE] as the target type
In the TreeView.ItemTemplate bind the DataContext AND Content property to [YOUR-DATA-TYPE], i.e.
<TreeView.ItemTemplate>
<DataTemplate x:DataType="[YOUR-DATA-TYPE]">
<TreeViewItem DataContext="{Binding}" ... Content="{Binding}">
<TreeViewItem.ContentTemplateSelector>
<YourDataTemplateSelector.TemplateA>
<DataTemplate x:DataType="[YOUR-DATA-TYPE]">
...
// YourDataTemplateSelector
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item == null) return null;
return (([YOUR-DATA-TYPE])item).IsSomething ? TemplateA : TemplateB;
}
I am having a little bit of a conundrum with my newest App.
It's a Master-Detail WPF MVVM App that uses MVVM Light and Fluent.Validation.
The View's DataContext is a MainViewModel : ViewModelBase with an ObservableCollection<ProviderDto> for the ListView on the left and a property ProviderDto SelectedProvider for the detailed Properties on the right.
There are also several RelayCommands to Add, Edit and Delete single ProviderDto's
The ViewModel uses a ProviderService to perform These Actions, which is injected in it's constructor with mvvmlight's SimpleIoC in a separate ViewModelLocator.
Everything works fine so far, I also managed to have Design-Time-Data.
I now tried to add Fluent.Validation to the Mix and implemented like it is described in this post (My ProviderDto now inherits from a ValidationBase instead of ObservableObject. The Base now inherits from ObservableObject. Also I registered the ProviderDtoValidator in the ViewModelLocator.)
This allows me to have my ObservableObjects have automatically validated and to call .IsValid on them.
So far so good, I am sure I will be able to make it work up to the View and make those error boxes get red :).
Now to my real question:
I want to have a Button on the View to Save the changes on the SelectedProvider. This should naturally be bound to this:
Relaycommand SaveProviderCommand = new RelayCommand(SaveProvider, CanSaveProvider)
private bool CanSaveProvider()
{
return SelectedProvider.IsValid;
}
private void SaveProvider()
{
if (SelectedProvider.IsValid)
_providerController.SaveProvider(SelectedProvider);
}
Where do I put the SaveProviderCommand SaveCommand?
If I put it in the ViewModel then I can only call it from the SelectedProvider-Property:
public ProviderDto SelectedProvider
{
get { return _selectedProvider; }
set
{
Set(() => SelectedProvider, ref prV_selectedProvider, value);
SaveProviderCommand.RaiseCanExecuteChanged(); // Here!
}
}
Which obviously doesn't work when just a single property in the SelectedProvider gets changed.
The other possibility is to put the Command on the DTO itself and call it everytime a property gets Changed. For instance when the Email-Property is changed:
//A Property from Provider
public string Email
{
get { return _email; }
set
{
Set(() => Email, ref _email, value.TrimSafe());
SaveProviderCommand.RaiseCanExecuteChanged(); // Here!
}
}
The Advantage here is that the Validation works out-of-the-box up to the View-Level when I Change every property. The disadvantage is that I would have to inject the ProviderController in the DTO's constructor so it can be called in the private Save-Method. I don't think the DTO should know how to save itself. It only should be able to tell if it .IsValid and the Saving logic should belong to the ViewModel.
I hope you can see my Dilemma:
If I put the SaveCommand in the ViewModel, then I will have to do
I-dont-know-what to validate my SelectedProvider in the View. How would the Validation work? I looked into DataTemplating the Controls but I don't seem to be able to make it work together with Fluent.Validation..
If I put SaveCommand in the DTO itself then the Validation works nicely but I don't think it's correct to inject so many capabilities in something that should stay dumb.
Of course this is a boiled-down example, but I think it's enough to illustrate the Problem. Hope to get some good advice on Patterns and practices.
I found a suitable solution for this Problem, maybe it's going to help somebody else.
Since both the SelectedProvider in the Viewmodel and the single Properties of it implement INotifyPropertyChanged (trhough ViewModelBase or ObservableObject) I can simply subscribe to the SelectedProvider.PropertyChanged in the ViewModel.
public MainViewModel()
{
// Constructor
if (SelectedProvider != null)
SelectedProvider.PropertyChanged += SelectedProvider_PropertyChanged;
}
private void SelectedProvider_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
SaveProviderCommand.RaiseCanExecuteChanged();
}
In the View I can implement the Controls according to this post
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Grid DockPanel.Dock="Right" Width="16" Height="16"
VerticalAlignment="Center" Margin="3 0 0 0">
<Ellipse Width="16" Height="16" Fill="Red"/>
<Ellipse Width="3" Height="8"
VerticalAlignment="Top" HorizontalAlignment="Center"
Margin="0 2 0 0" Fill="White"/>
<Ellipse Width="2" Height="2" VerticalAlignment="Bottom"
HorizontalAlignment="Center" Margin="0 0 0 2"
Fill="White"/>
</Grid>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder/>
</Border>
</DockPanel>
</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>
</Window.Resources>
<TextBox Name="TxtEmail" Margin="5" Text="{Binding Path=SelectedProvider.Email,
UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, Mode=TwoWay}" />
This Approach gives me the Separation of Concerns that I wanted and the Nice out-of-the-box Validation. The only small disadvantage: I don't find it very aesthetical to have an Event-Subscription in the VM...
I'm very new to data binding in WPF.
Let's say I have a class called FileSource, which has one property called File (a string) and some other properties that are derived from that. In my GUI, I have a control whose appearance should change between two "modes": one mode if File is null, another mode if it's not null. Let's say that one mode sets the visibility of some child components to Visible and others to Collapsed, while the other mode does the opposite.
I can think of 3 ways to go around this:
In the FileSource, create another property of type Visibility and return the proper visibility for each control. But this to me sounds very bad - it sounds like I'll be intimately mixing the "model" (FileSource) with the behavior of the view (the control).
Create lots of trivial data conversion classes, then do data binding with a semantic property of the model (File, in this case). For example, a string -> Visibility converter for some of the components and another string -> Visibility converter (which returns the "opposite" Visibility value) for the other components. This works and plays well with the property change notifications, but creating a new class for every kind of different response I expect from the sub-controls sounds needlessly complicated to me.
Write an Update method and subscribe to the PropertyChanged event. This sounds to me like I'll be largely defeating the point of data binding.
What is the correct way to do this? Is there, perhaps, a simple way to do a data "conversion" inline in XAML (for a value I intend to read, but not write back to the source)?
You don't need too many converter classes. You need only one BoolToVisibilityConverter, but with properties which specify visibility values for true and false. You create instances like this:
<BoolToVisibilityConverter x:Key="ConvertBoolToVisible"
FalseVisibility="Collapsed" TrueVisibility="Visible" />
<BoolToVisibilityConverter x:Key="ConvertBoolToVisibleInverted"
FalseVisibility="Visible" TrueVisibility="Collapsed" />
Another converter is IsNullConverter. You can parametrize it with a property like bool InvertValue. In your resource dictionary, instances can be called ConvertIsNull and ConvertIsNotNull. Or you can create two classes if you like.
And finally, you can chain converters with ChainConverter which chains multiple value converters. You can find sample implementation in my private framework (permalink). With it, you can create converter instances in XAML like ConvertIsNotNullToVisibleInverted. Sample usage:
<a:ChainConverter x:Key="ConvertIsNotNullToVisible">
<a:ValueConverterRef Converter="{StaticResource ConvertIsNotNull}"/>
<a:ValueConverterRef Converter="{StaticResource ConvertBoolToVisible}"/>
</a:ChainConverter>
An alternative is to use triggers. XAML code will be more complex though, so I prefer converters. It requires writing some classes, but it's worth it. With architecture like this, you won't need tens of classes for every combination, and both C# and XAML code will be simple and readable.
And don't add all possible combinations of converters. Add them only when you need them. Most likely, you'll need only a few.
Consider using visual states -- these are designed for the kind of scenario you are talking about, where you have a control that needs to transition between multiple states. One advantages to using this approach over bindings, is that it allows you to use animations (including transitions).
To get it working, you declare your visual state groups, and visual states, underneath the root element of your control:
<UserControl>
<Grid x:Name="LayoutRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="DefaultStates">
<VisualState x:Name="State1" />
<VisualState x:Name="State2">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="textBlock2"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" To="Visible" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock x:Name="textBlock1" Text="state #1" />
<TextBlock x:Name="textBlock2" Text="state #2" Visibility="Collapsed" />
</Grid>
</UserControl>
To transition between states, you can call VisualStateManager.GoToState(this, "State2", true). You can also use the Blend SDK to transition via triggers from XAML. Probably the most useful way to transition, is to use DataStateBehavior, which binds states to a view-model property:
<Grid x:Name="LayoutRoot">
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding CurrentState}"
Value="State2"
TrueState="State2" FalseState="State1" />
</i:Interaction.Behaviors>
This way you can just update a property in your view-model, and the UI state will update automatically.
public string File
{
get { return _file; }
set
{
_file = value;
RaisePropertyChanged();
RaisePropertyChanged(() => CurrentState);
}
}
private string _file;
public string CurrentState
{
get { return (File == null ? "State1" : "State2"); }
}
Option (2) is essentially what you are going for here. You need an IValueConverter (or 2, depending on implementation).
I would call it NullToVisibilityConverter or something like that. It would return Visiblity.Visible if the value argument is not null, and Visibility.Collapsed if it is. To swap behaviors, you could just write a second converter, or utilize the ConverterParameter.
It would look like:
public class NullToVisibilityConverter : IValueConverter
{
public object Convert(...)
{
return value != null ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(...)
{
return Binding.DoNothing;
}
}
With usage:
<Button Visibility="{Binding File, Converter={StaticResource MyConverter}"/>
And.... here is another way using styles/triggers:
MainWindow.xaml
<Window x:Class="WpfApplication19.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<StackPanel.Resources>
<Style TargetType="TextBlock" x:Key="FileIsNull">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding File}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBlock" x:Key="FileIsNotNull">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding File}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Text="Filename is null" Style="{StaticResource FileIsNull}" MinHeight="50" Background="Beige" />
<TextBlock Text="{Binding File}" Style="{StaticResource FileIsNotNull}" MinHeight="50" Background="Bisque" />
<Button Name="btnSetFileToNull" Click="btnSetFileToNull_Click" Content="Set File To Null" Margin="5" />
<Button Name="btnSetFileToNotNull" Click="btnSetFileToNotNull_Click" Content="Set File To Not Null" Margin="5" />
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System.ComponentModel;
using System.Windows;
namespace WpfApplication19
{
public partial class MainWindow : Window
{
public FileSource fs { get; set; }
public MainWindow()
{
InitializeComponent();
fs = new FileSource();
this.DataContext = fs;
}
private void btnSetFileToNull_Click(object sender, RoutedEventArgs e)
{
fs.File = null;
}
private void btnSetFileToNotNull_Click(object sender, RoutedEventArgs e)
{
fs.File = #"C:\abc.123";
}
}
public class FileSource : INotifyPropertyChanged
{
private string _file;
public string File { get { return _file; } set { _file = value; OnPropertyChanges("File"); } }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanges(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I'm trying to create a custom header for some of the columns in my generic DataGrid; I want these headers to include a text box which I can use to apply fiters to the data.
This is what I have so far:
<Style x:Key="ColumnHeaderStyle" TargetType="dataprimitives:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding}"/>
<TextBox Width="Auto" Tag="{Binding Path=Tag}" LostFocus="TextBox_LostFocus" TextChanged="TextBox_TextChanged" MinWidth="50"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
This is the style used by the header I'm toying with at the moment. Here's the code generation which feeds the header the appropriate data on creation:
private void dataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
(...)
e.Column.Header = new { Text = e.Column.Header, Tag = e.PropertyName };
e.Column.HeaderStyle = Resources["ColumnHeaderStyle"] as Style;
(...)
}
When the application is ran, the TextBlock of a column will contains the following: { Text = Description, Tag = Description }
As such, I'd expect the Path=Tag part of the binding to work, however, when the TextChanged event is hit, the tag is null.
What am I doing wrong?
Apparently using anonymous types doesn't work well with XAML and binding... which is odd, as it uses reflection, as far as I know.
Creating a simple public class for storing the data and using it instead of the anonymous type solved the problem.