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
Related
It's a way to modify the height on the window until remains only the window bar? Right now I use to set the height of the window to 0 BUT still remain some content above the window bar (that white and gray area), I want to remove this completely and leave only the window bar:
I use MVVM so binding is needed:
XAML:
Width="{Binding MainWindowWidthSize, Mode=TwoWay}"
Height="{Binding MainWindowHeightSize, Mode=TwoWay}"
C#
public void TriggerFloatingMode(object obj)
{
if (!_isFloatingModeEnabled)
{
MainWindowWidthSize = 500;
MainWindowHeightSize = 0;
_isFloatingModeEnabled = true;
}
else
{
MainWindowWidthSize = 1000;
MainWindowHeightSize = 560;
_isFloatingModeEnabled = false;
}
}
Add this code WindowStyle="None" in the Window tag and enter the following code at the bottom of the Window tag
<Window.Resources>
<Style TargetType="{x:Type local:MainWindow}">
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CornerRadius="0" GlassFrameThickness="0" ResizeBorderThickness="2" CaptionHeight="0"></WindowChrome>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
In the above code, change TargetType="{x:Type local:MainWindow}" to the name of your window local: your window name
An easy solution with your code could be
<Window x:Class="YourWindow.MainWindow"
SizeToContent="WidthAndHeight">
<Grid Height="{Binding MainWindowHeightSize }" Width="{Binding MainWindowWidthSize }">
</Grid>
</Window>
<DataTemplate x:Key="dataTempl">
<!--<Border BorderBrush="Coral" BorderThickness="1" Width="Auto" Margin="2">-->
<Button Background="{Binding background}" Name="btn" Tag="{Binding oID}" Click="btn_Click" Style="{StaticResource MetroButton}" Margin="1">
(... rest of items here ...)
</StackPanel>
</Button>
<!--</Border>-->
</DataTemplate>
As you can see, button have Style and background. Style from Resources contain border, background (as gradient) etc.
Now background element from my class:
public Brush background
{
get
{
SolidColorBrush clr = null;
if (backgroundString != "")
{
clr = new SolidColorBrush((Color)ColorConverter.ConvertFromString(backgroundString));
}
return clr;
}
}
But problem is that, it could contains color like #FFFF0000 or just be null.
What I'd like to do is :
if (backgroundString != "") -> apply background
else leave style as it was before.
But with code I show you, if it return null, style does change (there is no borders etc.)
Any idea?
Thanks!
What you want to do is a trigger.
You would like to use the default background, but override it when a given property meet a given condition.
You can do this easily with a trigger.
Simply add a property such as this one to your view model:
public bool OverrideBackground { get { return backgroundString != ""; } }
Then add the following trigger in your DataTemplate:
<DataTemplate>
[...]
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding OverrideBackground}" Value="true">
<Setter Property="Button.Background" Value="{Binding background}" TargetName="btn"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
The DataTrigger will be activated when the OverrideBackground property is true (that is, when backgroundString != ""), and will set the Background property of the Button (that you named btn in your code snippet) to the value of the background property of the bound view model.
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)
{
}
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
I have one TextBlock having width say 100. When the text length is a large one I want to show the characters that is accomodated in that textblock and a (...) button besides the text to specify user that more text is also there. Upon click on that (...) button, the full text will be shown in a separate pop up window.
So i want how the dynamic (...) button will be shown whenever the text length exceed the size of the textblock. Please answer
This isn't exactly what you want, but it's a similar idea and just uses the baked-in stuff:
<TextBlock MaxWidth="200"
Text="{Binding YourLongText}"
TextTrimming="WordEllipsis"
ToolTip="{Binding YourLongText}" />
So you have a TextBlock with a maximum width, and when the text can't fit it displays an ellipsis ("..."). Hovering over the TextBlock with your mouse will show the full text in a ToolTip.
Just experience the same requirement for adding ellipsis on button so adding the solution here
<Style x:Key="editButton" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center" >
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"></Setter>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Transparent"/>
</Trigger>
</Style.Triggers>
</Style>
Notice the resources in content presenter.
I believe what you want is to set the TextTrimming property. Settng it to WordElilipsis or CharacterEllipsis should provide what you need.
My solution to the problem is probably overkill, but allows for some configuration and control.
I created a behavior that allows me to set the character limit for each binding.
internal class EllipsisStringBehavior
{
public static readonly DependencyProperty CharacterLimitDependencyProperty = DependencyProperty.RegisterAttached("CharacterLimit", typeof(int), typeof(EllipsisStringBehavior), new PropertyMetadata(255, null, OnCoerceCharacterLimit));
public static readonly DependencyProperty InputTextDependencyProperty = DependencyProperty.RegisterAttached("InputText", typeof(string), typeof(EllipsisStringBehavior), new PropertyMetadata(string.Empty, OnInputTextChanged));
// Input Text
public static string GetInputText(DependencyObject dependencyObject)
{
return Convert.ToString(dependencyObject.GetValue(InputTextDependencyProperty));
}
public static void SetInputText(DependencyObject dependencyObject, string inputText)
{
dependencyObject.SetValue(InputTextDependencyProperty, inputText);
}
// Character Limit
public static int GetCharacterLimit(DependencyObject dependencyObject)
{
return Convert.ToInt32(dependencyObject.GetValue(CharacterLimitDependencyProperty));
}
public static void SetCharacterLimit(DependencyObject dependencyObject, object characterLimit)
{
dependencyObject.SetValue(CharacterLimitDependencyProperty, characterLimit);
}
private static void OnInputTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textblock = (TextBlock)d;
string input = e.NewValue == null ? string.Empty : e.NewValue.ToString();
int limit = GetCharacterLimit(d);
string result = input;
if (input.Length > limit && input.Length != 0)
{
result = $"{input.Substring(0, limit)}...";
}
textblock.Text = result;
}
private static object OnCoerceCharacterLimit(DependencyObject d, object baseValue)
{
return baseValue;
}
}
I then simply add the using to my user control...
<UserControl
xmlns:behavior="clr-namespace:My_APP.Helper.Behavior"
d:DesignHeight="300" d:DesignWidth="300">
...and apply the behavior to the TextBlock control I wish to use it on.
<TextBlock Margin="0,8,0,8"
behavior:EllipsisStringBehavior.CharacterLimit="10"
behavior:EllipsisStringBehavior.InputText="{Binding Path=DataContext.FeedItemTwo.Body, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignSubheadingTextBlock}"
FontSize="14"/>
Hope this helps.