I created a custom control which inherits from toolbar.
I would like that the default control template of the toolbar will contain a couple of default buttons.
In order to achieve this, I created a static array to hold the button list:
<x:Array x:Key="toolbarButtons" Type="{x:Type ToggleButton}">
<ToggleButton Content="Bold"
Command="{x:Static ns1:EditingCommands.Bold}"
CommandTarget="{Binding}"
IsChecked="{Binding IsBold, Mode=TwoWay}"/>
<ToggleButton Content="Italic"
Command="{x:Static ns1:EditingCommands.Italic}"
CommandTarget="{Binding}"
IsChecked="{Binding IsItalic, Mode=TwoWay}"/>
</x:Array>
The toolbar control has datacontext which is bounded to a text editor which includes all of the command bindings and the boolean dependency properties (IsBold, IsItalic).
I set the Toolbar ItemSource to use the array like this:
<Setter Property="ItemsSource" Value="{StaticResource toolbarButtons}"/>
Now, when I open a window who hosts the toolbar for the first time on a given run, everything works great.
The problem is, when I close the window, and reopen it, the button bindings stop working (IsCheked property stops being connected to the dependency property).
I used snoop to check the bindings, and it says that the value of IsChecked is local, which means the binding is ignored.
I suspect that the problem is my array is a static resource, so the toolbar uses the same instance from time to time, and this somehow ruins the binding.
My question is how to solve this, or maybe should I use a different approach in order to achieve default buttons for my toolbar?
I think the problem is that when you declare your array in XAML, it's only instantiated once. So, the second toolbar (and on) are trying to use the exact same objects. When it's reused, your bindings are likely being overwritten.
Have you tried adding x:Shared="False" to the array declaration?
<x:Array x:Key="toolbarButtons" x:Shared="False" Type="{x:Type ToggleButton}">
Related
Trying to set the Content property of a WPF DataGrid top-left button at run time. I get the button object using the VisualTreeHelper of the DataGrid object and then I successfully set its Content property, as verified using Snoop while running the application. However, the button text is not visible. I suspect this is because there are UI elements on top of the button that use non-transparent background brushes. Upon reading the docs I see a grid that uses storyboards and a rectangle that uses gradient brushes.
Other than editing the WPF DataGrid top-left button style template, what are my options for making the button Content (text) visible?
You could entirely replace the template.
I'd prefer to bind the text of the textblock to a property in a viewmodel personally.
This could get you started.
Put this in scope of the datagrid like in your window resources or a resource dictionary merged in app.xaml.
<Style x:Key="{ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle, TypeInTargetAssembly={x:Type DataGrid}}" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="Red">
<TextBlock Text="X" Foreground="White"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Needs more work, but it shows up on a datagrid sample I have.
( This is something which has dozens of styles from experiments answering questions elsewhere in it ).
(Something funny happens, I am now posting under the same user name as before but SO insists that I must recreate my profile every time I log in. Go figure.)
Thank you Andy for the solution you proposed. For the technical reason above, I am unable to mark this question "Solved".
Now I think a quick way around this is to place a transparent label right on top of the DataGrid button, with IsHitTestVisible = false. I noticed a couple interesting things though:
It seems the button can have a Grid or a string for Content but not both;
The button is already created when the DataGrid Loaded event handler runs, e.g., a Click event handler can be added to it; however, setting the Content to a string at this time doesn't change anything. I guess Content changes when the DataGrid is populated.
I have a WPF/MVVM app with a ListBox which displays data through a DataTemplate. I managed to change the selected item in the ListBox when pressing a button so the CommandParameter is linked to the ListBox's SelectedItem, but I cannot get the buttons to be enabled/disabled correctly in the same way. For example, if I have 2 items and the button should be enabled in one and disabled in the other, when I select an element BOTH buttons have the same state, and they BOTH change state when I select another item.
I am using a RelayCommand as used in many MVVM Frameworks.
Here is my XAML (removed "not interesting" parts):
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<Grid>
<Button Content="Something" Name="EnabledDisabledButton" Click="Button_Click"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SomeCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}, Path=SelectedItem}"/>
</Grid>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}" x:Key="ContainerStyle">
<Setter Property="ContentTemplate" Value="{StaticResource ItemTemplate}" />
</Style>
</UserControl.Resources>
<ListBox x:Name="myListBox" ItemsSource="{Binding ElementList}"
IsSynchronizedWithCurrentItem="True" ItemContainerStyle="{StaticResource ContainerStyle}"/>
I tried to pass the SelectedItem as a parameter to the RelayCommand's CanExecute method, but the result was the same as before.
Is there a way to pass the actual ListBoxItem in which the button "lives in" as a parameter to the command, so each one will be processed separately by the CanExecute method? Would it work if I got this? (right now I am handling the Click event to select the correct item in the list before executing the command).
In my CanExecute method I am evaluating some property of the SelectedItem in order to enable/disable the corresponding button. An alternative would be to evaluate this property for all elements, but I cannot think of a way to do it inside the ViewModel, and then communicate to the view the result (if it is even possible while using a DataTemplate for the items).
Thanks for your input, regards!
Converting My comment into an answer:
Why not just CommandParameter="{Binding}"?
You mention "MVVM" in the question, but it seems you use the MVVM way to your full advantage.
I would not have a Button_Click event in the style at all. That is because it is in fact a style, which per definition could be changed to another style which does not have the same event, which again will make the application stop working as wanted if you choose to have a style-based app in the future.
A rule I use is that a style is a style. A style has to do with the UI and "looks" of the app.
Functionality should be separate from the UI. The programmer can define the Command, and the designer can decide how the user will use that in the best way.
That's exactly where the code separation from the MVVM pattern cames into grip.
To separate the "looks" and user behavior and the app's logic.
Like...it should not matter to the model if a command fires from a button, a menu, a datacontext or a key stroke.
If this particular problem was handled to ME, I would solve it by having a HOLDER-class.
This is a class (DependencyObject which implements INotifyPropertyChanged) that holds a ICommand property as well as the "row" that will be displayed in the various rows in the ListBox.
The ICommand property will be bound to the Button, having the row (class) itself as CommandParameter to the call.
Then the actual row would be used in the ItemTemplate on the ListBox, with Bindings to different elements (proprty with or withouy Converters) to make whatever desired display available.
I hope I explained good enough...
Feel free to ask more if you want more details to my solution alternative.
I've got a ContentControl which has a style containing a border and other visual decorations. I want these decorations to disappear when the content is collapsed, so I figured I have to set the visibility of the ContentControl to collapsed in this case. I got this style for my ContentControl decoration:
<Style x:Key="DecoratedItem1" TargetType="{x:Type c:DecoratedItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:DecoratedItem}">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="2" CornerRadius="2">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/file.png"/>
<ContentPresenter Name="wContent"/>
</StackPanel>
</Border>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
<DataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger.Setters>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The DecoratedItem class is just a subclass of ContentControl with additional DependencyProperties which are not relevant to this issue, I just wanted to note that I already have a subclass to which I could add code, if necessary.
This works when the content of the ContentControl is a UIElement, however if the content is generated by a DataTemplate it complains about not being able to find the Visibility property.
<!-- works -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
<!-- doesn't work -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
<c:DecoratedItem.Resources>
<DataTemplate DataType="{x:Type clr:String}">
<TextBlock Text="{Binding}" Visibility="Collapsed"/>
</DataTemplate>
</c:DecoratedItem.Resources>
</c:DecoratedItem>
The error for the second case diplayed in the debug output window is:
System.Windows.Data Error: 40 : BindingExpression path error:
'Visibility' property not found on 'object' ''String' (HashCode=-885832486)'.
BindingExpression:Path=Content.Visibility;
DataItem='ContentPresenter' (Name='wContent');
target element is 'DecoratedItem' (Name='');
target property is 'NoTarget' (type 'Object')
I understand why this happens, but don't know how to fix my style to work as I want it. I don't mind adding helper code to the DecoratedItem subclass if necessary. Any idea how to fix this?
[edit 1]
Some more explanation in regard to the proposed answer:
I can't enforce that the Content is always an UIElement. This is a model-view design after all, and of course I simplified the example a lot. In the real project the content is a model selected from the DataContext, which can be of several different types, and the DataTemplate builds a presentation for that model. Some of the DataTemplates decide (depending on model-state) that there is nothing to present and switch Visibility to Collapsed. I would like to propagate that information to the decorating container. The example above really just presents the problem and not the motivation, sorry.
[edit 2]
Not sure how knowing more about the model would help the problem, but here we go. The data in the Content field doesn't have much in common since it can be a lot of things, this DecoratedItem is supposed to be reusable to give a common visual style to items shown on some forms. Content can be stuff like work items whose DataTemplate collapses them if they are disabled; other kinds of Content can be incomplete and get collapsed. Of course other kinds never may get collapsed.
But note that the data model doesn't really have much to do with the question, which still is how to bind against the Visibility of the expanded content element (after possibly exposing it through the subclass in a bindable way).
There are a couple of ways of describing what's wrong. In the first, working example:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
the Content property of the ContentControl is set to be a TextBlock, which is a UIElement with a Visibility property. (This assumes that you have not changed the ContentPropertyAttribute of your derived class DecoratedItem to be something other than Content). Thus, your DataTrigger binding can correctly evaluate:
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
Contrast the working case with the failing one:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
in which the Content property is set to an instance of a String, which does not have a Visibility property.
The other way to describe what's wrong is to note that, even though you supply a DataTemplate for the case of Content being a String, Content is still a String and still does not have a Visibility property. In other words, the statement that the Content is generated by the DataTemplate is incorrect -- your DataTemplate just told the control how to display Content of type String.
The general answer to your question is that, if you want the DataTrigger in DecoratedItem1 bound with a Path of Content.Visibility, you need to make sure the content you put in it is always a UIElement. Conversely, if you want to be able to put any sort of Content into the control, you need to trigger off of something else.
The specific answer to your question, strictly, relies on your broader intent (in particular, on how the Visibility of the Content of your control will be set/modified). A couple of possibilities:
if you really want your DataTrigger binding of the form, "Content.Visibility", make sure that the Content is always a UIElement. For instance, use the working form of the style and then bind the Text of TextBlock to something appropriate. However, this doesn't fit so well with the idea of your derived control as a ContentControl, so...
your DataTrigger could probably bind to something else. It seems like, from the way the question is formed, that there is some other property or code-behind that will control whether the various content entities are Visible or not.
finally, you could add an additional DataTrigger to the TextBlock. This DataTrigger would set the visibility of its parent based on its own visibility. Then, bind the DataTrigger in style DecoratedItem1 with Path "Visibility" instead of "Content.Visibility", essentially chaining together Visibilities manually.
Edit
Based on what you've described about how you want to use this, it sounds like you need to consider the visual tree. You might augment your DecoratedItem control to have the following functionality: if all its visual children that are UIElments have a visibility of Collapsed (or if it has no visual children), it is also Collapsed (or, whatever logic makes sense for the desired functionality in terms of the Visibility of its visual children). You'd need to use the VisualTreeHelper class from code -- in particular, the GetChildrenCount and GetChild methods. You'd also, in your DecoratedItem class, override OnVisualChildrenChanged (while still calling the base class method) so that you can get UIElement.IsVisibleChanged events for the visible children.
I have a list on my WPF xaml which contains two items. Below is the Style template for each item. Now on UI this shows like a group of radio buttons(No. of radio buttons depends on no. of items in my list).
<Style x:Key="RadioButtonListBoxItemStyle" TargetType="{x:Type ListBoxItem}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<RadioButton FlowDirection="LeftToRight"
Margin="10 15"
Content="{Binding Value}"
GroupName="{Binding DisplayGroupName}"
IsChecked="{Binding IsSelected, Mode=TwoWay}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now I bind a list(having 2 items) using the above style template to get two radio buttons. What happens is everything works pretty fine i.e when I change the selection of radio button on UI the IsSelected property is getting updated properly to true or false depending on whether my radio is checked/un-checked. But if I try to set the list in the code manually, then from that point my binding of the radio button's with my list is lost and nothing happen's.
Any help on this would be great and based on my needs I have to set the list in the code manually. So is there any solution in a way that binding will not be lost even though I set the list in my code manually. Thanks.
-Ady.
This is a common problem with radio buttons in WPF, and it has to do an unusual aspect of binding, one that is marginally more feature than bug.
The design of binding assumes that the only two things that change the value of a binding's target property are a) actions in the UI and b) changes to the source property. If you set the target property of a binding in code - like, you explicitly set the Background of a Border, even though it has a binding - the binding decides that you know what you're doing, and that it should just get out of the way. So it turns itself off.
This is a pretty sensible design decision, for the most part. It's better than throwing an exception, for instance. Most of the time, you're not going to ever set IsEnabled in code anyway; you'll let the binding do it. Especially if you're using MVVM.
Okay, so what happens if you have radio buttons in a group?
When you check one button in the group, the WPF code that manages radio button groups unchecks all the other buttons in the group, by setting IsChecked to false in code. The binding disables itself. Oops.
Here's the solution: If you're using radio buttons and binding, don't use groups. Handle the mutual exclusion logic in your view model code. In your case, code your view models so that only one object in a collection can have IsSelected true at any given time. (Yes, this is a pain.)
The radio buttons will still work as expected, but since the only properties being set by code are the source properties, binding won't break.
you are setting the style for the listboxitem class, including the bindings. so, when you set the list from code behind it does not contain listboxitems, it contains the items from your list. so, the style does not apply. what you should do is make the <DataTemplate> for the type of item in your list--in effect telling WPF what you want each item to look like.
<DataTemplate TargetType="{x:Type MyCustomClass}" >
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Deleteable, Mode=TwoWay}" />
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</DataTemplate>
(this is off the top of my head, so the xaml might not be exactly right)
I've got a TabItem contanining a listbox, which has an obeservable collection of my feeds class as its item source. When I refresh/load the feeds into the collection I want to disable the main window so that the user can't go clicking other things while this process is running. So I set tbCtrl.isEnabled=false; to my tab control on the form. Then assign an event handler to the a custom finish event which is triggered after all the feeds are loaded.
This all works fine, however the hyperlinks for the results which are currently displayed on the tab control never get re-enabled (Nor do the next few which are out of view due to the list box size). All the other results further down are fine, as are the results on the other tab.
I've tried calling InvalidateVisual on the tab control after everything is finished, to see if that makes a difference but that doesn't seem to cause any change.
I could understand it if it was all Hyperlinks doing it, or just the ones currently displayed, but I don't understand why ones which are out of scroll are not working either.
I hit the same issue.
What I did is to bind HyperLink's IsEnabled to the parent, and put that in an App global resource.
<Style TargetType="{x:Type Hyperlink}">
<Setter Property="IsEnabled" Value="{Binding IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type FrameworkElement}}}" />
</Style>
I found the answer for my case of the hyperlink not getting re-enabled, not sure if it applies to yours:
I found that when the Hyperlink's parent control is disabled (IsEnabled=false), the Hyperlink will not get notified of changes, e.g. IsEnabledChanged does not get fired, even when the bound property changes value.
My solution was to change my Xaml to no longer disable the ancestor control (which was causing the Hyperlink's parent to be disabled). With the parent (TextBlock) always enabled, now Hyperlink updates properly always.
(I'm a little bothered that the IsEnabled binding behaves differently than Controls do, and I'm not sure what I would do if I couldn't leave the ancestor enabled... but at least this lets me understand the issue I was having, and lets me work around it.)
Details: WPF 3.5 SP1
It's not just HyperLinks. It seems to be more specifically TextBlock which of course is what you use to wrap a HyperLink in WPF. This will give the same issue :
<TextBlock>
<Run Text="Barcode:"/>
<InlineUIContainer BaselineAlignment="Center">
<TextBox Text="{Binding OriginalPackage.BarcodeNumber}" />
</InlineUIContainer>
</TextBlock>
I was hoping setting IsEnabled="True" would fix it but it doesn't seem to.
The easy solution is to use a StackPanel with Orientation="Horizontal"