I'm creating a WPF app using the MVVM design pattern, and I'm trying to extend the TabItem control so that it closes the tab when the user clicks the middle mouse button. I'm trying to achieve this using InputBindings, and it works very well until I try to define it within a style. I've learned that you cannot add InputBindings to a style unless you attach it using a DependencyProperty. So I followed this similar post here... and it works... almost. I can close one tab using the middle mouse button, but it won't work on any of the other tabs (all of the tabs are added at runtime and inherit the same style).
So I need some help. Why would this only be working the first time, and not after? Obviously I could create a custom control that inherits from a TabItem and make it work, but I'd like to figure this out as I can see this being expanded in my projects. I'm no expert on DependencyProperties, so please help me out. Thanks!
Style:
<Style TargetType="{x:Type TabItem}">
<Setter Property="w:Attach.InputBindings">
<Setter.Value>
<InputBindingCollection>
<MouseBinding MouseAction="MiddleClick"
Command="{Binding CloseCommand}"/>
</InputBindingCollection>
</Setter.Value>
</Setter>
...
</Style>
Class
public class Attach
{
public static readonly DependencyProperty InputBindingsProperty =
DependencyProperty.RegisterAttached("InputBindings", typeof(InputBindingCollection), typeof(Attach),
new FrameworkPropertyMetadata(new InputBindingCollection(),
(sender, e) =>
{
var element = sender as UIElement;
if (element == null) return;
element.InputBindings.Clear();
element.InputBindings.AddRange((InputBindingCollection)e.NewValue);
}));
public static InputBindingCollection GetInputBindings(UIElement element)
{
return (InputBindingCollection)element.GetValue(InputBindingsProperty);
}
public static void SetInputBindings(UIElement element, InputBindingCollection inputBindings)
{
element.SetValue(InputBindingsProperty, inputBindings);
}
}
Your class "Attach" worked fine for me!
If anyone needs, the trick is use style like this, with the x:Shared modifier:
<InputBindingCollection x:Key="inputCollection" x:Shared="False">
<KeyBinding Key="Del" Command="{Binding DeleteItemCommand}"/>
</InputBindingCollection>
<Style TargetType="{x:Type TabItem}">
<Setter Property="w:Attach.InputBindings" Value="{StaticResource inputCollection}" />
...
</Style>
Thanks!
Never mind, I figured it out myself. I ended up not even using the Attach class above... instead I used InputBindings on the ControlTemplate for the TabItem (which is a Border), so it looked something like this... I don't know why I didn't think of this in the first place.. :)
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid SnapsToDevicePixels="true">
<Border x:Name="Bd" ...>
<DockPanel>
...
</DockPanel>
<Border.InputBindings>
<MouseBinding MouseAction="MiddleClick"
Command="{Binding CloseCommand}"/>
</Border.InputBindings>
</Border>
</Grid>
...
</ControlTemplate>
Related
I might be just missing something or probably today is not my day but what I am trying to do keeps failing.
I have a custom control called MyContentControl. It looks like this:
public class MyContentControl : ContentControl
{
static MyContentControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyContentControl), new FrameworkPropertyMetadata(typeof(MyContentControl)));
}
public DockPanel DifferentLook
{
get;
set;
}
public string Txt
{
get;
set;
}
protected override void OnInitialized(EventArgs e)
{
if (this.DifferentLook != null)
{
this.Content = this.DifferentLook;
}
Binding b = new Binding("Txt");
b.Source = this;
this.SetBinding(ContentProperty, b);
base.OnInitialized(e);
}
}
This is its theme:
<Style TargetType="{x:Type local:MyContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyContentControl}">
<Border Background="{TemplateBinding Background}" Margin="{TemplateBinding Padding}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is MainWindow:
<local:MyContentControl Txt="texty text">
<local:MyContentControl.DifferentLook>
<DockPanel>
<TextBlock Text="Content = " DockPanel.Dock="Left"/>
<ContentPresenter DockPanel.Dock="Left"/>
</DockPanel>
</local:MyContentControl.DifferentLook>
</local:MyContentControl>
When no "DifferentLook" specified I would like the control to use the ContentPresenter defined inside the default ControlTemplate.
If I have DifferentLook set then it should display the control in different look.
See the method OnInitalized.
Now the problem is when I apply DifferentLook it seems that the DifferentLook.ContentPresenter is not working.
Why is DifferentLook.ContentPresenter not appyling the content correctly?
The output on window is "texty text" but it should be "content = texty text".
EDIT: This is the light version. I created this and kept things as simple as possible to demostrate the problem. In real the custom control is a bit huge and user may not override ControlTemlates.
Do you guys have an idea how to solve this with the given requirements?
Buddy I am not quite agreed with the approach you are taking here. But I can point out what you are doing wrong and why you are not able to see your DockPanel.
Since you have defined the ControlTemplate for your ContentControl having ContentPresenter, then any Content you set on your ContentControl will be placed inside the ContentPresenter of your ControlTemplate defined in style whether it is the Content set by Binding or Directly placed inside on control (like DockPanel here)
In your example, OnInitialized() function first set the Content to the DockPanel and then immediatly set Binding on Content. The moment Binding is set it will override the previously set DockPanel with the Source value which is string Txt(texty text).
So if you remove the Binding setting from Oninitialized you will see your DockPanel. But wait there is one more thing. Inside the Content of your ContentPresenter with Content bound as Content="{Binding}" which will make your programme to go into infinite recursion as it will try to bind it to self and on and on. So you will have to set the Content Binding of inner ContentPresenter to some property of Parent ContentControl Context. So just for testing try removing the content binding from inner contentpresenter and give some static value.
I actually have two questions:
How do I modify a custom control that inherits from a FrameworkElement that only holds one child (e.g. Page or ContentControl) so that it holds multiple children (like a Panel)? Can this be done from the class definition?
How do I bind the children of a custom control to a Panel object (e.g WrapPanel) defined in the template of the custom control?
I'm not even sure if it is possible, but I want to create a custom control that behaves like a Page, or may even inherit from Page, but allows me to enter in children in XAML. For example, I would like the XAML for generating my custom control to look like this:
<CustomPage CustomAttribute="blah">
<TextBox/>
<TextBlock Text="hehe"/>
<Label Content="ha!"/>
</CustomPage>
I want to define in the style that a WrapPanel displays the children. Something like this:
<Style TargetType="{x:Type CustomPage}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CustomPage}">
<WrapPanel/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
I would replace the WrapPanel with a ContentPresenter except that I want the ContentPresenter to behave like a WrapPanel.
I hope this is specific enough.
You can actually do exactly what you want already with an ItemsControl.
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<TextBox/>
<TextBlock Text="hehe"/>
<Label Content="ha!"/>
</ItemsControl.Items>
</ItemsControl>
Hope that helps
EDIT: You can use the above ItemsControl as the root element in a page to gain the page's functionality.
Thank you, Murkaeus. You got me going in the right direction.
In the end, I created a custom control that inherited from Page, defining a ContentProperty for it. This apparently overrides the ContentProperty of the base class. And this works for any control type, too. Here is my code:
[ContentProperty("Children")]
class CustomPage : System.Windows.Controls.Page
{
ObservableCollection<UIElement> children = new ObservableCollection<UIElement>();
public ObservableCollection<UIElement> Children { get { return children; } set { children = value; } }
}
Then I defined the template for my control in the Themes/Generic.xaml file with an ItemsControl using my custom ContentProperty as a source:
<Style TargetType="local:CustomPage">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ItemsControl ItemsSource="{Binding Children,
RelativeSource={RelativeSource TemplatedParent}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I hope this helps anyone else who is looking for a solution to the same problem. And thank you again, Murkaeus, for your help!
EDIT: I should warn everyone that if you use this method, all your data bindings are lost for some reason, I discovered this with further experimentation. I ended up giving up and just specifying a panel as the child of my custom page object.
I have a visio-like interfact but have actual model data behind some of the elements. The elements can be moved by the user.
I use a contentcontrol on a canvas whereby the viewmodels of the elements are places in the content which can then be displayed differently depending on their type but using the same contentcontrol. It is simple to bind the view to the different properties in the viewmodel. However, I have to save the position in the model, and I cannot find a binding solution.
1) The Application.Save Command is handled in the main view model, so I do not have access to the view there. That means I must save the postion data when the elements are moved, or is there a better approach?
2) Assuming that I am right with 1), I am looking to avoid code behind, i.e. I do not want the contentcontrol to deal with the elements that they have in their content. However, so far the code behind version is all I could come up with:
My code behind solution so far:
All model elements implement an interface:
public interface IViewElement
{
String Position { get; set; }
}
And in the contentcontrol:
void ContentControl_LayoutUpdated(object sender, EventArgs e)
{
IViewElement content = this.Content as IViewElement;
content.Position = new Point(Diagram.GetLeft(this), Diagram.GetTop(this)).ToString();
}
The XAML:
<Style TargetType="{x:Type diagram:Item}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type diagram:Item}">
<Grid Canvas.Top="{Binding ElementName=PART_ContentPresenter, Path=Content.Position, Mode=TwoWay}" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
ContextMenu="{x:Null}">
<!-- PART_ContentPresenter -->
<ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<DataTemplate DataType="{x:Type local:ViewModel}">
<StackPanel>
...
</StackPanel>
Just encapsulate the codebehind you've used in a Behavior
Why are you using a string to store the position? Use either a Point or two decimal values, and then bind your ContentControl's Canvas.Top and Canvas.Left position to these values using two-way binding.
It will automatically update the model when the Top and Left positions change.
Edit:
Here's an example:
<ContentControl Canvas.Top="{Binding ContentModel.Top, Mode=TwoWay}"
Canvas.Left="{Binding ContentModel.Left, Mode=TwoWay}"
Content="{Binding ContentModel}" />
Need your help. I have a ListBox (with virtualization) which displays a ScrollViewer.
My ListBox items are expandable, and while expanded their hight may exceed the visible scrolling area.
The problem i'm expiriencing is that when the list box item is exceeds the visible scrolling area - scrolling jumps to the next ListBox item rather than simply scrolling the view.
Check this code:
<ListBox Grid.Row="1" Grid.Column="0" DataContext="{Binding SpecPackageSpecGroupListViewModel}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"
ItemContainerStyle="{StaticResource SpecPackageSpecGroupListBoxStyle}" ScrollViewer.IsDeferredScrollingEnabled="True"
ItemsSource="{Binding SortedChildren}" ScrollViewer.CanContentScroll="True"
Background="Transparent"
BorderThickness="0" SelectionMode="Extended"
Margin="5,5,5,5">
<ListBox.ItemTemplate>
<DataTemplate>
<Controls:SpecPackageSpecGroupControl/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Of-course, i can't wrap my ListBox with another scroller since it will turn off the visualization (which is very impotent to me).
If i set CanContentScroll to False everything works as expected - but the virtualization stops working.
HELP!!!
Gili
Ok, so just before i was about to give up and somehow learn how to live with this bug i bumped into a post (which i can't seem to find now) that suggests that TreeView does support Pixel-Based scrolling (AKA Physical Scrolling) without turning off visualization.
So i tried this and indeed - it works! Made sure to verify that virtualization works, tested with ~1000 items and also set a break point on my control constructor and made sure it is called when my view is scrolled.
The only disadvantage of using TreeView instead of ListBox is that TreeView doesn't seem to support multiple item selection (which i needed) - but implementing this is way much easier than implementing the smart scrolling for ListBox.
I created a style for TreeViewItem that makes the TreeViewItem look and behave just like ListBoxItem, this is really not mandatory - but i preferred it like this (beside the fact that the basic style has stretching issues which i had to fix with styling). Basically i removed the ItemsPresenter and stayed only with the ContentPresenter since my data is not hierarchical:
<Style x:Key="MyTreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Border Name="myBorder"
SnapsToDevicePixels="true"
CornerRadius="0,0,0,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderThickness="0"
BorderBrush="Transparent"
Height="Auto"
Margin="1,1,1,3"
Background="Transparent">
<ContentPresenter Grid.Column="1" x:Name="PART_Header" HorizontalAlignment="Stretch" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentSource="Header"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now - the only thing i've got left to do is implement the multi-selection tree view.
There might be different approaches to implement such behavior, i took the ViewModel approach.
Derived from TreeView i created a new MultiSelectionTreeView:
public class MultiSelectionTreeView : TreeView
{
private static bool CtrlPressed
{
get
{
return Keyboard.IsKeyDown(Key.LeftCtrl);
}
}
protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
{
base.OnSelectedItemChanged(e);
var previouseItemViewModel = e.OldValue as IMultiSelectionTreeViewItemViewModel;
if (previouseItemViewModel != null)
{
if (!CtrlPressed)
previouseItemViewModel.IsSelected = false;
}
var newItemViewModel = e.NewValue as IMultiSelectionTreeViewItemViewModel;
if (newItemViewModel != null)
{
if (!CtrlPressed)
newItemViewModel.ClearSelectedSiblings();
newItemViewModel.IsSelected = true;
}
}
}
Where IMultiSelectionTreeViewItemViewModel is as follows:
public interface IMultiSelectionTreeViewItemViewModel
{
bool IsSelected { get; set; }
void ClearSelectedSiblings();
}
Of course - now it is my responsibility to handle the way selected items are being presented - in my case it was given since my tree view items had their own DataTemplate which had indication for its selection.
If this is not your case and you need it, simply extent your tree view item data template to indicate its selection state according to its view model IsSelected property.
Hope this will help someone someday :-)
Have fun!
Gili
This question is still coming up in search engines, so I'll answer it 2 years later.
WPF 4.5 now supports pixel based virtualizing panels.
If you can target 4.5, then just add this to your ListBox:
<Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel"/>
For what's new in .NET 4.5 (which includes new stuff in WPF 4.5) see http://msdn.microsoft.com/en-us/library/ms171868.aspx
Take a look here (Bea Stollnitz) and here(Dan Crevier); basically you'll need to implement your own container that supports both virtualization and pixel-based scrolling. You can also look at this similar SO question for more details or a possible alternative. Deriving from VirtualizingStackPanel and modifying the scroll behavior seems to be the best bet.
I'm having a heck of a time trying to template bind the StrokeThickness of a rectangle.
My goal is to allow a user of my custom control to set a property called SelectedBorderThickness which will, in fact, set the StrokeThickness of a rectangle.
I thought I understood templating but I guess I really don't.
If I do this:
<Rectangle x:Name="myRect" Height="100" Width="100" Stroke="Black" SelectedBorderThickness="5" />
Can someone please show me how to write the Style elements to get this to work?
You should add more details to the question and people will be able to help you more easily. I think I have figured out what you want though.
You are looking to make a custom templated silverlight control, containing a bunch of elements incluiding a rectangle in its template. You would like a user to be able to set the thickness of that rectangle inside the control with a property on the control itself. From what you put above, I don't know how much you have written in your code -- so I will just post a nearly complete example of what you are after.
First I created a templated custom control in visual studio, and added the new dependancy property we want a user to be able to set:
public class TestControl : Control
{
static public DependencyProperty SBTProperty { get; set; }
static TestControl()
{
SBTProperty = DependencyProperty.Register("SelectedBorderThickness", typeof(double), typeof(TestControl),null);
}
public TestControl()
{
this.DefaultStyleKey = typeof(TestControl);
}
public double SelectedBorderThickness
{
get { return (double)GetValue(SBTProperty); }
set { SetValue(SBTProperty, value); }
}
}
Then I set up the template in Generic.xaml (for my example the only thing I have in my control is the rectangle since I don't know what you want in there):
<Style TargetType="local:TestControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TestControl">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Rectangle Fill="Bisque" Stroke="Black" StrokeThickness="{TemplateBinding SelectedBorderThickness}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now I am all set up to use it from xaml in other parts of my application. For my example, I put one right in the center of my MainPage:
<local:TestControl SelectedBorderThickness="75"></local:TestControl>
EDIT:
After reading your code below, I see now what the problem is. You're trying to do a template binding, but the way you have it it's going to try to bind to the current template, which is the template for listboxitem and not your custom listbox. What you really want in this situation is to do a RelativeBinding with FindAncestor to jump up the tree to the template of your custom listbox, but MS hasn't yet implemented that kind of binding in Silverlight (even though it's pretty common in WPF). Luckily in your specific situation we can finagle the right object through the path in a TemplatedParent binding, without having to write a bunch of messy codebehind to emulate an ancestor binding:
StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Parent.SelectedBorderThickness}"
Dump that into the Rectangle in your template posted above and it should work -- it will access the content of the ListBoxItem (which is whatever you are displaying), and then access that objects Parent (which will be your custom listbox). From there we just hit up the property we set up before.
If you want a cleaner solution, join the chorus of us asking MS to implement ancestor binding in Silverlight.
Here's the problem section, it's when I'm attempting to style the ItemContainerStyle for my custom control which derives from a ListBox:
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="{TemplateBinding Background}">
<!-- VSM stuff removed for clarity -->
<ContentPresenter
x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"/>
<Rectangle x:Name="FocusVisualElement"
Stroke="Goldenrod"
StrokeThickness="{TemplateBinding SelectedBorderThickness}"
Visibility="Collapsed"
RadiusX="1"
RadiusY="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
The problems is that when I set StrokeThickness = {TemplateBinding SelectedBorderThickness} on the Rectangle and then try to use the control in a test app, I get a ParserError:
Message: Unknown attribute StrokeThickness on element Rectangle
If I hardcode the StrokeThickness to 3 (or whatever), it parses fine and I can view the test app.
In the end, all I'm really trying to do is create a property that shows up in Intellisense so that my (eventual) end users of my custom control can change the color and border thickness, radius, etc. of the highlight on a hovered and selected ListBoxItem in a dynamically bound custom ListBox. It shouldn't be this dang hard.
The dang comments are too restricted. I'm not trying to answer my own question (I wish I could).
David, your code works fine when you add ListBoxItems statically. When adding them dynamically, the thickness doesn't change. To test this out, I added a new TestControl in MainPage:
<StackPanel>
<local:TestControl SelectedBorderThickness="9" x:Name="h1n1">
<TextBlock Text="Honk1"></TextBlock>
<TextBlock Text="Honk2"/>
</local:TestControl>
<local:TestControl x:Name="SwineFlu" SelectedBorderThickness="20" />
</StackPanel>
In the code-behind I added:
ObservableCollection<string> test = new ObservableCollection<string>();
test.Add("Hi David");
test.Add("Hello World");
SwineFlu.ItemsSource = test;