Here is my XAML. The UserControl is named "Event"
<UserControl.Resources>
<Style x:Key="eventStyle" TargetType="Thumb">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Rectangle Name="rect" Fill="CadetBlue" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Canvas>
<Thumb Canvas.Left="0" Canvas.Top="0" Name="MoveThumb" Style="{StaticResource eventStyle}" Cursor="SizeAll" DragDelta="MoveThumb_DragDelta" DragStarted="MoveThumb_DragStarted" DragCompleted="MoveThumb_DragCompleted" />
</Canvas>
And here is the code behind
var ev = new Event();
var rect = ev.Template.FindName("rect", ev) as Rectangle;
But it doesn't work : the "rect" variable is null. What am I doing wrong ?
Thanks
The template you're defining is applied to the Thumb control, and not the Event control - that's why there's no rect control in Event's template.
Since you're creating the Event control from another class, what you can do is expose the MoveThumb control as a property in Event's code-behind, like this:
public Thumb TheThumb
{
get { return MoveThumb; }
}
Then you can change your code to this:
var ev = new Event();
var rect = ev.TheThumb.Template.FindName("rect", ev.TheThumb) as Rectangle;
Better yet, you can expose the rect control as a property:
public Rectangle Rect
{
get { return MoveThumb.Template.FindName("rect", MoveThumb) as Rectangle; }
}
and use it like this
var ev = new Event();
var rect = ev.Rect;
It returned null because the function FindName("controlName",TemplatedParent) expects a control on which the template is applied as the second parameter. From the code you've provided, I couldn't see when the template was applied to the control (ev used to the default template). Hence, the rect variable was null.
Try this
var rectangle = MoveThumb.Template.FindName("rect", MoveThumb) as Rectangle;
More information is available here and here
Related
I need to read values like Padding etc. stored in Style of Button (or other FrameworkElement). How to do this?
For example if I make a Style for Button like this:
Style style = new Style(typeof(Button));
style.Setters.Add(new Setter(Button.HeightProperty, 70));
MyButton.Style = style;
So... How I can read later for example the Setter HeightProperty? And what about in case below? How to get Padding?
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
<ContentPresenter x:Name="ContentPresenter"
Padding="11,15,7,0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
I have tried to get information by
Style ButtonStyle = MyButton.GetStyle();
but after this I don't get it at all how to continue.
In first case you can get the currently applied value using the GetValue mehtod:
var value = (double)MyButton.GetValue(Button.HeightProperty);
Or even more simply:
var value = MyButton.Height;
In second case the problem is a bit more complicated, as the Padding is part of the template itself, not the button. To access it, you will need the following helper method:
public IEnumerable<TChildType> FindChildren<TChildType>(DependencyObject parent)
{
var count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is TChildType typedChild)
{
yield return typedChild;
}
foreach (var nestedChild in FindChildren<TChildType>(child))
{
yield return nestedChild;
}
}
}
This traverses the VisualTree under a parent and searches for descendants of certain type. We can use it like this:
var contentPresenter = FindChildren<ContentPresenter>(MyButton).First();
Debug.WriteLine(contentPresenter.Padding);
Make sure to call the FindChildren method only after the page is actually loaded (for example in the Page.Loaded event handler, or OnNavigatedTo), as in the Page constructor, the template children do not exist yet and the helper would return no children.
I need to apply style in grid resources programmatically in code behind.
I've the following snippet of code :
<Grid x:Name="grid">
<Grid.Resources>
<Style TargetType="{x:Type ig:LabelPresenter}">
<EventSetter Event="PreviewMouseMove" Handler="LabelPresenter_PreviewMouseMove"/>
</Style>
</Grid.Resources>
.
.
.
</Grid>
I want to create the Style in code behind and add this to resources for handle the relative action.
I tried to do this in this way but it dosen't work.
public MainWindow()
{
InitializeComponent();
var style = new Style { TargetType = typeof(LabelPresenter) };
var eventSetter = new EventSetter(PreviewMouseMoveEvent, new MouseButtonEventHandler(LabelPresenter_PreviewMouseMove));
style.Setters.Add(eventSetter);
grid.Resources.Add("style", style);
}
Where I'm wrong?
Thanks in advance.
EDIT: I wrote wrong grid's name. The grid's right name is grid
The style defined in the XAML markup is implicit, i.e. it has no x:Key. So change the first argument that you are passing to the Add method to typeof(LabelPresenter).
Also, a PreviewMouseMove event handler accepts a MouseEventArgs:
var style = new Style { TargetType = typeof(LabelPresenter) };
var eventSetter = new EventSetter(PreviewMouseMoveEvent, new MouseEventHandler(LabelPresenter_PreviewMouseMove));
style.Setters.Add(eventSetter);
grid.Resources.Add(typeof(LabelPresenter), style);
I have a Overlay over my WPF Applicaiton, it shows some boarders as "context sensitive help". The boarders should now overrule the parent background and show the content behind (some kind of a view Port through the background).
The Controls look like this without Overlay:
With the Overlay Activated it looks like this:
The Overlay is a Usercontrol containing a ListBox of Items it should supply a boarder to.
The ListBoxPanel is a Canvas and the ListBoxItems are the Boarders(Buttons) you can see, which are moved over the UIElements they should surround using a ItemContainerStyle.
The Overlay looks like this:
<ItemsControl ItemsSource="{Binding HelpItems}" KeyboardNavigation.TabNavigation="Cycle" IsTabStop="True"
helpers:FocusHelper.FocusOnLoad="True" FocusVisualStyle="{StaticResource EmptyFocusVisual}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Button Command="{Binding ShowPopupCommand}" Background="Transparent" BorderThickness="2">
<Button.Style>
<Style>
<Setter Property="Button.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
<Setter Property="FrameworkElement.Width" Value="{Binding Width}" />
<Setter Property="FrameworkElement.Height" Value="{Binding Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
I want the overlay to be transparent inside the boarder, while keeping the semitransparent dimming background on the list box, in other words the empty space of my ListBox should be Gray.
Is there any easy way I can get the Light Blue border to show the Content behind the panel without the semi transparent background of my Overlay?
This is the target result:
I did as well try to create a Opacity filter but it is the wrong way around. And it does not seem there is a easy way to invert a opacity filter.
Console,
Ok we have "to make some holes in the ice".
So here is a custom control : OverlayWithGaps that draws itself, with a given background that can be semi transparent.
OverlayWithGaps has a Rect Collection that represents the gaps :
public ObservableCollection<Rect> Gaps
{
get { return (ObservableCollection<Rect>)GetValue(GapsProperty); }
set { SetValue(GapsProperty, value); }
}
private static FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
null,
null
);
public static readonly DependencyProperty GapsProperty =
DependencyProperty.Register("Gaps", typeof(ObservableCollection<Rect>), typeof(OverlayWithGaps), fpm);
With AffectsRender, if that dependency property changes redrawing will happen.
Here is the drawing function :
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
if (Gaps != null && Gaps.Count > 0)
{
Geometry newGeometry = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
foreach (var gap in Gaps)
// remove each rectangle of the global clipping rectangle :
// we make "a hole in the ice"
newGeometry = Geometry.Combine(newGeometry,
new RectangleGeometry(gap),
GeometryCombineMode.Exclude,
transform: null);
// When the geometry is finished, we make the hole
dc.PushClip(newGeometry);
}
dc.DrawRectangle(Background, null, new Rect(0, 0, ActualWidth, ActualHeight));
}
EDIT
3. Rectangles are provided from the ItemsControl ListViewItems
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// overlay is the OverlayWithGaps instance
// in the window
overlay.Gaps = new ObservableCollection<Rect>(
itemsControl1.FindAllVisualDescendants()
.OfType<Grid>()
.Select(grid => {
Point relativePoint = grid.TransformToAncestor(this)
.Transform(new Point(0, 0));
return new Rect(relativePoint.X,
relativePoint.Y,
grid.ActualWidth,
grid.ActualHeight
);
})
);
}
Note that I select Grids in the LINQ query, because they are in the DataTemplate.
But the Linq query could select nearly anything (by name, ...)
The FindAllVisualDescendants() extension function can be found here :
Datagrid templatecolumn update source trigger explicit only updates first row
Here is the full working solution : http://1drv.ms/1OO2gWk
Best coding
So I originally was going to just throw marker classes on the canvas manually in code behind, but then I thought, hmm, I shouldn't really be throwing actual viewobjects into the code inside my view model. So then I thought, "Let me just have a collection and then add to it when i want the View to update."
However, I'm having issues. The problem I am having is that the line I wish to display is not appearing at the desired location.
My goal was to have a small thumb control that I had customized with a template that I could then place in a canvas and use the canvas.setLeft(double) function to set the position of the thumb. This worked fine when I placed it inside a canvas. However, as soon as I switched to the following itemscontrol, instead of displaying the line at the desired location, it always displays the line at coordinate x = 0.
When I call canvas.getleft() on the thumb control, it returns the desired coordinate. However, that is not where it is displaying. I have created a test case to narrow down outside factors and figure out what is going on.
<Application.Resources>
<Style x:Key="CanvasMarkerStyle" TargetType="{x:Type Comp:CanvasMarker}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="StrokeThickness" Value="3"/>
<Setter Property="Width" Value="3"/>
<Setter Property="Height" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualHeight}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Comp:CanvasMarker}">
<Line
Stroke="Orange"
StrokeThickness="{Binding RelativeSource={RelativeSource AncestorType={x:Type Comp:CanvasMarker}},
Path=StrokeThickness}"
X1="0"
Y1="0"
X2="0"
Y2="{Binding RelativeSource={RelativeSource AncestorType={x:Type Comp:CanvasMarker}}, Path=ActualHeight}"
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
and the Itemscontrol:
<ItemsControl
x:Name="ItemsControl"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Grid.ZIndex="1"
Background="Gray"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas
x:Name="MarkerCanvas"
SnapsToDevicePixels="false"
/>
</ItemsPanelTemplate>
<Comp:CanvasMarker
MarkerTime="50"
/>
</DataTemplate>
On Loaded, I take MarkerTime and Canvas.SetLeft(this, MarkerTime);
I feel like there is an obvious reason why I cannot use canvas.setleft. My guess is that it has something to do with how canvas manages its attached properties. I am guessing that it uses them during layout to place the child correctly. I think that maybe in my case where the attached.left property is changed inside an itemspanel, it doesn't prompt another layout or something. Anyway, an explanation or suggestions would be greatly appreciated.
I figured doing it this way was much more MVVM than just creating view items on a canvas manually from my view model with the correct properties that i desired.
but having a control so I can bind an items collection seems to be more of a headache than i anticipated.
public CanvasMarker()
{
this.Unloaded += new RoutedEventHandler(CanvasMarker_Unloaded);
this.Initialized += new EventHandler(CanvasMarker_Initialized);
this.Loaded += new RoutedEventHandler(CanvasMarker_Loaded);
this.DragStarted += new DragStartedEventHandler(CanvasMarker_DragStarted);
this.DragDelta += new DragDeltaEventHandler(CanvasMarker_DragDelta);
this.PreviewMouseDown += new System.Windows.Input.MouseButtonEventHandler(CanvasMarker_PreviewMouseDown);
this.Style = (Style)Application.Current.Resources["CanvasMarkerStyle"];
}
void CanvasMarker_Loaded(object sender, RoutedEventArgs e)
{
gvvm = Tag as GraphViewerViewModel;
var p = VisualTreeUtilities.FindParent<DependencyObject>(this);
var gp = VisualTreeUtilities.FindParent<DependencyObject>(p);
var ggp = VisualTreeUtilities.FindParent<DependencyObject>(gp);
var gggp = VisualTreeUtilities.FindParent<DependencyObject>(ggp);
ParentCanvas = VisualTreeUtilities.FindParent<Canvas>(this) as Canvas;
double MarkerHorizontalPositionInPixels = MarkerTime / gvvm.UnitsOfTimePerPixel;
SetMarker(MarkerHorizontalPositionInPixels);
}
void CanvasMarker_Initialized(object sender, EventArgs e)
{
}
void CanvasMarker_Unloaded(object sender, RoutedEventArgs e)
{
}
static void SetMarkerToNewPosition(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CanvasMarker cm = d as CanvasMarker;
if (cm.IsLoaded)
{
double MarkerHorizontalPositionInPixels = cm.MarkerTime / cm.gvvm.UnitsOfTimePerPixel;
cm.SetMarker(MarkerHorizontalPositionInPixels);
if (cm.IsCurrentMarker)
{
cm.gvvm.MarkerExists = true;
cm.gvvm.CalculateValuesAtPrimaryMarker();
}
}
}
void CanvasMarker_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
}
void CanvasMarker_DragDelta(object sender, DragDeltaEventArgs e)
{
double NewXPosition = XPositionInPixels + e.HorizontalChange;
SetMarker(NewXPosition);
}
void SetMarker(double PositionInPixels)
{
if (PositionInPixels < StrokeThickness / 2)
{
PositionInPixels = StrokeThickness / 2;
}
else if (PositionInPixels > ParentCanvas.ActualWidth)
{
PositionInPixels = ParentCanvas.ActualWidth;
}
Canvas.SetLeft(this, PositionInPixels);
double d = Canvas.GetLeft(this);
XPositionInPixels = PositionInPixels;
}
void CanvasMarker_DragStarted(object sender, DragStartedEventArgs e)
{
}
I'm trying to use the context menu in a listview to run some code that requires data from which item it originated from.
I initially just did this:
XAML:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.Resources>
<ContextMenu x:Key="resourceContextMenu">
<MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click" />
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource resourceContextMenu}" />
</Style>
</ListView.ItemContainerStyle>
...
C#:
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
// code that needs item data here
}
But I found that the originating listview item was not accessible that way.
I've read some tactics about how to get around this, like intercepting the MouseDown event and setting a private field to the listviewitem that was clicked, but that doesn't sit well with me as it seems a bit hacky to pass data around that way. And WPF is supposed to be easy, right? :) I've read this SO question and this MSDN forum question, but I'm still not sure how to really do this, as neither of those articles seem to work in my case. Is there a better way to pass the item that was clicked on through to the context menu?
Thanks!
Similar to Charlie's answer, but shouldn't require XAML changes.
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
MenuItem menu = sender as MenuItem;
ListViewItem lvi = lvResources.ItemContainerGenerator.ContainerFromItem(menu.DataContext) as ListViewItem;
}
Well in the cmMetadata_Click handler, you can just query the lvResources.SelectedItem property, since lvResources will be accessible from the code-behind file that the click handler is located in. It's not elegant, but it will work.
If you want to be a little more elegant, you could change where you set up your ContextMenu. For example, you could try something like this:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<TextBlock Text="{TemplateBinding Content}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</ListView.Style>
<ListViewItem>One Item</ListViewItem>
<ListViewItem>Another item</ListViewItem>
</ListView>
What this does is plug in a template for your ListViewItem, and then you can use the handy TemplatedParent shortcut to assign the ListViewItem to the DataContext of your menu item.
Now your code-behind looks like this:
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
MenuItem menu = sender as MenuItem;
ListViewItem item = menu.DataContext as ListViewItem;
}
Obviously the downside is you will now need to complete the template for a ListViewItem, but I'm sure you can find one that will suit your needs pretty quickly.
So I decided to try and implement a command solution. I'm pretty pleased with how it's working now.
First, created my command:
public static class CustomCommands
{
public static RoutedCommand DisplayMetadata = new RoutedCommand();
}
Next in my custom listview control, I added a new command binding to the constructor:
public SortableListView()
{
CommandBindings.Add(new CommandBinding(CustomCommands.DisplayMetadata, DisplayMetadataExecuted, DisplayMetadataCanExecute));
}
And also there, added the event handlers:
public void DisplayMetadataExecuted(object sender, ExecutedRoutedEventArgs e)
{
var nbSelectedItem = (MyItem)e.Parameter;
// do stuff with selected item
}
public void DisplayMetadataCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
I was already using a style selector to dynamically assign styles to the listview items, so instead of doing this in the xaml, I have to set the binding in the codebehind. You could do it in the xaml as well though:
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
MyItem selectedItem = (MyItem)item;
Style s = new Style();
var listMenuItems = new List<MenuItem>();
var mi = new MenuItem();
mi.Header= "Get Metadata";
mi.Name= "cmMetadata";
mi.Command = CustomCommands.DisplayMetadata;
mi.CommandParameter = selectedItem;
listMenuItems.Add(mi);
ContextMenu cm = new ContextMenu();
cm.ItemsSource = listMenuItems;
// Global styles
s.Setters.Add(new Setter(Control.ContextMenuProperty, cm));
// other style selection code
return s;
}
I like the feel of this solution much better than attempting to set a field on mouse click and try to access what was clicked that way.