I'm still struggling with my custom panel for my GridView (see other question: UWP c# Binding to ItemsPanel / Custom Panel (Template))
I have set up my GridView with my custom panel. Now if I switch my GridView Selection Mode from 'Multiple' to 'Single' and vice-versa, the items in the custom panels are not updated automatically => the 'selection square' doesn't show/hide for all items at once, only if I runs my mouse over it.
This doesn't happe if I use an ItemsWrapGrid for instance... Which Event /Method do I need to implement in the custom panel?
So far my code of the panel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using teachers_cal;
using teachers_cal.Classes;
namespace teachers_cal.UserControls
{
public class PRGD010_GridViewPanel : Panel
{
public int NumberRowsOrColumns
{
get { return (int)GetValue(NumberRowsOrColumnsProperty); }
set { SetValue(NumberRowsOrColumnsProperty, value < 1 ? 0 : value); }
}
public static readonly DependencyProperty NumberRowsOrColumnsProperty = DependencyProperty.Register("NumberRowsOrColumns", typeof(int), typeof(PRGD010_GridViewPanel), new PropertyMetadata(1, OnNumberRowsOrColumnsPropertyChanged));
private static void OnNumberRowsOrColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as PRGD010_GridViewPanel).InvalidateMeasure();
}
public int Offset
{
get { return (int)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value >= this.NumberRowsOrColumns ? this.NumberRowsOrColumns - 1 : value); }
}
public static readonly DependencyProperty StartPositionProperty = DependencyProperty.Register("Offset", typeof(int), typeof(PRGD010_GridViewPanel), new PropertyMetadata(0, OnStartPositionPropertyChanged));
private static void OnStartPositionPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as PRGD010_GridViewPanel).InvalidateMeasure();
}
public PRGD010_GridViewPanel()
{
}
/// Get Items Source count items
private int GetItemsCount()
{
return this.Children != null ? this.Children.Count : 0;
}
/// Get Calculated Row or Column count. Depending on Offset, Numbers and Items
private int GetRowsOrColumnsCount()
{
int additionalLine = ((GetItemsCount() + this.Offset)) % this.NumberRowsOrColumns > 0 ? 1 : 0;
return ((GetItemsCount() + this.Offset)) / this.NumberRowsOrColumns + additionalLine;
}
/// <summary>
/// Arrange all items
/// </summary>
protected override Size ArrangeOverride(Size finalSize)
{
// Clip to ensure items dont override container
this.Clip = new RectangleGeometry { Rect = new Rect(0, 0, finalSize.Width, finalSize.Height) };
Double positionTop = 0d;
double positionGrid = this.Offset;
double test = this.NumberRowsOrColumns;
// Must Create looping items count
foreach (UIElement item in this.Children)
{
if (item == null)
continue;
Size desiredSize = item.DesiredSize;
if (double.IsNaN(desiredSize.Width) || double.IsNaN(desiredSize.Height)) continue;
// Get rect position
var rect = new Rect(positionGrid * desiredSize.Width, positionTop, desiredSize.Width, desiredSize.Height);
item.Arrange(rect);
// set internal CompositeTransform to handle movement
TranslateTransform compositeTransform = new TranslateTransform();
item.RenderTransform = compositeTransform;
if (positionGrid < this.NumberRowsOrColumns - 1) { positionGrid += 1; }
else
{
positionGrid = 0;
positionTop += desiredSize.Height;
}
}
return finalSize;
}
/// <summary>
/// Measure items
/// </summary>
protected override Size MeasureOverride(Size availableSize)
{
//Size s = base.MeasureOverride(availableSize);
// set good cliping
//this.Clip = new RectangleGeometry { Rect = new Rect(0, 0, s.Width, s.Height) };
// Measure all items
foreach (UIElement container in this.Children)
{
container.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
}
double width = Children[0]?.DesiredSize.Width ?? 0;
double height = Children[0]?.DesiredSize.Height ?? 0;
var i = GetRowsOrColumnsCount();
return (new Size(width * (double)NumberRowsOrColumns, height * (double)GetRowsOrColumnsCount()));
//return (new Size(3000, 3000));
}
}
}
EDIT:
I've created two simple projects. Even with standard ItemPanels I got strange behavior. It's really frustrating
Using the GridView with a VariableSizeWrapGrid the GridViewItems will not be refresh properly when I change SelectionMode
Doing the same with ItemsWrapGrip is okay. Changing SelectionMode will be displayed correctly...
Could someone figure out why this happens? And what to do?!
See the two links...
https://onedrive.live.com/redir?resid=4305F58623B1701D!42317&authkey=!APnPBY_9SNDS4AI&ithint=file%2czip
https://onedrive.live.com/redir?resid=4305F58623B1701D!42316&authkey=!AMDZtSBQ5bjWaoU&ithint=file%2czip
Thanks for your reply
Related
I have made dependency services on both side and Interface in shared library on Xamarin platform
when i use it in xaml.cs hard coded it works but when i use it in ViewModel using messaging center to set count of notification in Toolbar of application it doesn't works
Does anyone have any kind of idea whats happening or should i do ?
this code works from Xaml.cs page
DependencyService.Get<IToolbarItemBadgeService>().SetBadge(this, ToolbarItems[0], "2", Color.Red, Color.White);
And Below Code doesn't works
From ViewModel
" MessagingCenter.Send(new MessagingCenterModel { }, "NotificationCount", NotificationListCount);
At Xaml.cs Page
MessagingCenter.Subscribe<MessagingCenterModel, string>(this, "NotificationCount", (sender, args) =>
{
DependencyService.Get<IToolbarItemBadgeService>().SetBadge(this, ToolbarItems[0], args, Color.Red, Color.White);
});
InterFaceCode
public interface IToolbarItemBadgeService
{
void SetBadge(Page page, ToolbarItem item, string value, Color backgroundColor, Color textColor);
}
Dependency service
public class ToolbarItemBadgeService : IToolbarItemBadgeService
{
public void SetBadge(Page page, ToolbarItem item, string value, Color backgroundColor, Color textColor)
{
Device.BeginInvokeOnMainThread(() =>
{
var toolbar = CrossCurrentActivity.Current.Activity.FindViewById(Resource.Id.toolbar) as Android.Support.V7.Widget.Toolbar;
if (toolbar != null)
{
if (!string.IsNullOrEmpty(value))
{
var idx = page.ToolbarItems.IndexOf(item);
if (toolbar.Menu.Size() > idx)
{
var menuItem = toolbar.Menu.GetItem(idx);
BadgeDrawable.SetBadgeText(CrossCurrentActivity.Current.Activity, menuItem, value, backgroundColor.ToAndroid(), textColor.ToAndroid());
}
}
}
});
}
}
BadgeDrawable Class
public class BadgeDrawable : Drawable
{
private const string BadgeValueOverflow = "*";
private Paint _badgeBackground;
private Paint _badgeText;
private Rect _textRect = new Rect();
private string _badgeValue = "";
private bool _shouldDraw = true;
Context _context;
public override int Opacity => (int)Format.Unknown;
public BadgeDrawable(Context context, Color backgroundColor, Color textColor)
{
_context = context;
float textSize = context.Resources.GetDimension(Resource.Dimension.textsize_badge_count);
_badgeBackground = new Paint();
_badgeBackground.Color = backgroundColor;
_badgeBackground.AntiAlias = true;
_badgeBackground.SetStyle(Paint.Style.Fill);
_badgeText = new Paint();
_badgeText.Color = textColor;
_badgeText.SetTypeface(Typeface.Default);
_badgeText.TextSize = textSize;
_badgeText.AntiAlias = true;
_badgeText.TextAlign = Paint.Align.Center;
}
public override void Draw(Canvas canvas)
{
if (!_shouldDraw)
{
return;
}
Rect bounds = Bounds;
float width = bounds.Right - bounds.Left;
float height = bounds.Bottom - bounds.Top;
float oneDp = 1 * _context.Resources.DisplayMetrics.Density;
// Position the badge in the top-right quadrant of the icon.
float radius = ((Java.Lang.Math.Max(width, height) / 2)) / 2;
float centerX = (width - radius - 1) + oneDp * 2;
float centerY = radius - 2 * oneDp;
canvas.DrawCircle(centerX, centerY, (int)(radius + oneDp * 5), _badgeBackground);
// Draw badge count message inside the circle.
_badgeText.GetTextBounds(_badgeValue, 0, _badgeValue.Length, _textRect);
float textHeight = _textRect.Bottom - _textRect.Top;
float textY = centerY + (textHeight / 2f);
canvas.DrawText(_badgeValue.Length > 2 ? BadgeValueOverflow : _badgeValue,
centerX, textY, _badgeText);
}
// Sets the text to display. Badge displays a '*' if more than 2 characters
private void SetBadgeText(string text)
{
_badgeValue = text;
// Only draw a badge if the value isn't a zero
_shouldDraw = !text.Equals("0");
InvalidateSelf();
}
public override void SetAlpha(int alpha)
{
// do nothing
}
public override void SetColorFilter(ColorFilter cf)
{
// do nothing
}
public static void SetBadgeCount(Context context, IMenuItem item, int count, Color backgroundColor, Color textColor)
{
SetBadgeText(context, item, $"{count}", backgroundColor, textColor);
}
public static void SetBadgeText(Context context, IMenuItem item, string text, Color backgroundColor, Color textColor)
{
if (item.Icon == null)
{
return;
}
BadgeDrawable badge = null;
Drawable icon = item.Icon;
if (item.Icon is LayerDrawable)
{
LayerDrawable lDrawable = item.Icon as LayerDrawable;
if (string.IsNullOrEmpty(text) || text == "0")
{
icon = lDrawable.GetDrawable(0);
lDrawable.Dispose();
}
else
{
for (var i = 0; i < lDrawable.NumberOfLayers; i++)
{
if (lDrawable.GetDrawable(i) is BadgeDrawable)
{
badge = lDrawable.GetDrawable(i) as BadgeDrawable;
break;
}
}
if (badge == null)
{
badge = new BadgeDrawable(context, backgroundColor, textColor);
icon = new LayerDrawable(new Drawable[] { item.Icon, badge });
}
}
}
else
{
badge = new BadgeDrawable(context, backgroundColor, textColor);
icon = new LayerDrawable(new Drawable[] { item.Icon, badge });
}
badge?.SetBadgeText(text);
item.SetIcon(icon);
icon.Dispose();
}
}
Firstly , make sure that we had Subscribe the message before we send it . Otherwise the codes in Subscribe will never been called .
In addition , the code in Dependency Service could only set the badge of ToolbarItem in the navigation bar (on tabbed bar it will never work).
If you want to set the badge of tabbed page icon , you could use the plugin Plugin.Badge .
I've decided to repose this question with the full code. I created a test case just demonstrating what i think is the weird behavior so that other people can run the code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
namespace Test
{
public class TestSizeVisual: FrameworkElement
{
public double MinimumXInDIPs
{
get { return (double)GetValue(MinimumXInDIPsProperty); }
set
{
SetValue(MinimumXInDIPsProperty, value);
}
}
public static readonly DependencyProperty MinimumXInDIPsProperty =
DependencyProperty.Register("MinimumXInDIPs",
typeof(double), typeof(TestSizeVisual),
new FrameworkPropertyMetadata(new double(), new PropertyChangedCallback(Redraw)));
public double ViewportWidth
{
get { return (double)GetValue(ViewportWidthProperty); }
set
{
SetValue(ViewportWidthProperty, value);
}
}
public static readonly DependencyProperty ViewportWidthProperty =
DependencyProperty.Register("ViewportWidth",
typeof(double), typeof(TestSizeVisual),
new FrameworkPropertyMetadata(new double(), FrameworkPropertyMetadataOptions.AffectsMeasure));
VisualCollection visuals;
protected override Size MeasureOverride(Size availableSize)
{
return new Size(50000000000, 50);
}
public TestSizeVisual()
{
visuals = new VisualCollection(this);
this.Loaded += new RoutedEventHandler(TestSizeVisual_Loaded);
}
void TestSizeVisual_Loaded(object sender, RoutedEventArgs e)
{
DrawSignal();
}
private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TestSizeVisual sg = d as TestSizeVisual;
if (sg != null && sg.IsLoaded)
{
sg.DrawSignal();
}
}
private void DrawSignal()
{
DrawingVisual signalbox = new DrawingVisual();
using (DrawingContext dc = signalbox.RenderOpen())
{
dc.DrawRectangle(Brushes.Orange, new Pen(Brushes.Black, 2), new Rect(new Point(0, 0), new Point(1000000000, ActualHeight)));
dc.DrawRectangle(Brushes.Purple, new Pen(Brushes.Black, 2), new Rect(new Point(MinimumXInDIPs, 2), new Point(MinimumXInDIPs + 10, 6)));
dc.DrawRectangle(Brushes.Purple, new Pen(Brushes.Black, 2), new Rect(new Point(MinimumXInDIPs + ViewportWidth - 10, 2), new Point(MinimumXInDIPs + ViewportWidth, 6)));
dc.DrawLine(new Pen(Brushes.Black, 2), new Point(MinimumXInDIPs, 10),
new Point(MinimumXInDIPs + ViewportWidth, 10));
}
visuals.Add(signalbox);
InvalidateVisual();
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return visuals.Count;
}
}
}
}
The xaml file looks like this:
<ScrollViewer
x:Name="scrollviewer1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
>
<test:TestSizeVisual
MinimumXInDIPs="{Binding ElementName=scrollviewer1, Path=HorizontalOffset}"
ViewportWidth="{Binding ElementName=scrollviewer1, Path=ViewportWidth}"
/>
</ScrollViewer>
The rectangles and lines stay centered on screen as you scroll for small sizes of the testsizevisual. However, once I alter the measureoverride to return a huge size, scrolling no longer results in correct centering of the drawings. Why does everything glitch at huge sizes? Do I need to code my own scrollviewer to accomodate larger amounts of content or is there a workaround?
So a friend finally figured this out for me. Even though i was doing calculations with doubles, WPF does drawing using direct x. Direct x is limited by floats. By checking the mantissa of the float on wiki, i found that I was exceeding the value of the mantissa for a float, thus the value was no longer accurate.
I am adding Rectangle from grid cell values that is being entered by user directly in grid rows. When i modify value of specific column say Thickness i.e Height then then it increases Height of selected row rectangle but it doesnt rearrange all rectangle below it exactly after the selected row rectangle.
In xaml.cs
public class MyLayer : INotifyPropertyChanged
{
public string Thickness { get; set; }
public string OffsetRight { get; set; }
public string OffsetLeft { get; set; }
public string Material { get; set; }
public string MaterialPopup { get; set; }
public Rectangle rectangle { get; set; }
public GlassRectangle GlassRectangle { get; set; }
public MaterialLayer()
{
GlassRectangle = new GlassRectangle();
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { }
remove { }
}
}
public class GlassRectangle
{
public Rectangle Rectangle { get; set; }
public double Top = 0;
public GlassRectangle()
{
Rectangle = new Rectangle();
}
}
private void gridInner_CellValueChanged(object sender, DevExpress.Xpf.Grid.CellValueChangedEventArgs e)
{
string cellValue = string.Empty;
MyLayer currentLayer = ((MyLayer)(e.Row));
if (e.Column.HeaderCaption.ToString() == "Thickness")
{
cellValue =(e.Value.ToString());
//there is alredy a rectangle - means this is edit mode
if (currentLayer.rectangle != null)
{
currentLayer.rectangle.Height = Convert.ToDouble(cellValue);
currentLayer.rectangle.Stroke = new SolidColorBrush(Color.FromRgb(0, 255, 0));
}
//else this is insert mode
else
{
currentLayer.rectangle = CreateRectangle(cellValue);
}
}
}
protected Rectangle CreateRectangle(string cellval)
{
Rectangle newrect = new Rectangle();
newrect.Stroke = Brushes.Red;
newrect.StrokeThickness = 1;
if (cellval.ToString().Contains("."))
{
newrect.Height = Convert.ToDouble(cellval) * 100;
}
else
{
newrect.Height = Convert.ToDouble(cellval);
}
newrect.Width = width;
Canvas.SetLeft(newrect, 100);
double canvasTop = 0.0;
if (canvasboard.Children.Count > 0)
{
var lastChildIndex = canvasboard.Children.Count - 1;
var lastChild = canvasboard.Children[lastChildIndex] as FrameworkElement;
if (lastChild != null)
//lastChild.Height-1: so that it come extactly on existing if set to +1 it comes below first rectangle
canvasTop = Canvas.GetTop(lastChild) + lastChild.Height - 1;
}
Canvas.SetTop(newrect, canvasTop);
val = val + 1;
newrect.Tag = val;
canvasboard.Children.Add(newrect);
//rectangle = rect;
foreach (UIElement ui in canvasboard.Children)
{
if (ui.GetType() == typeof(Rectangle))
{
itemstoremove.Add(ui);
}
}
return newrect;
}
NEW EVENT Method:
private void gridMaterialInner_CellValueChanged(object sender, DevExpress.Xpf.Grid.CellValueChangedEventArgs e)
{
string cellValue = string.Empty;
string cellOldValue = string.Empty;
MyLayer currentLayer = ((MyLayer)(e.Row));
if (e.Column.HeaderCaption.ToString() == "Thickness")
{
//current cell value
cellValue =(e.Value.ToString());// GetRowCellValue(e.RowHandle, gridMaterialInner.Columns["LastName"]).ToString();
//there is alredy a rectangle - means this is edit mode
double currentheight = 0.0;
double oldht = 0.0;
// old cell value
if (e.OldValue != null)
{
cellOldValue = (e.OldValue.ToString());
}
if (currentLayer.rectangle != null)
{
if (cellValue.ToString().Contains("."))
{
currentheight = Convert.ToDouble(cellValue) * 100;
}
else
{
currentheight = Convert.ToDouble(cellValue) * 100;
}
if (cellOldValue.ToString().Contains("."))
{
oldht = Convert.ToDouble(cellOldValue) * 100;
}
else if(cellOldValue!=string.Empty)
{
oldht = Convert.ToDouble(cellOldValue) * 100;
}
currentLayer.rectangle.Height = currentheight;
currentLayer.rectangle.Stroke = new SolidColorBrush(Color.FromRgb(0, 255, 0));
//Refresh();
//Get the index of selected row
int layerIndex = materialBindlist.IndexOf(currentLayer);
for(int i = layerIndex; i < materialBindlist.Count-1; i++)
{
//set the top position of all other rectangles that are below selected rectangle/row
//(Current-Old)+Top
Canvas.SetTop(materialBindlist[i + 1].rectangle, (currentheight - oldht) + materialBindlist[i + 1].GlassRectangle.Top);
//Canvas.SetTop(materialBindlist[i].rectangle, (currentheight - oldht) + materialBindlist[i + 1].GlassRectangle.Top);
}
}
//else this is insert mode
else
{
//MaterialLayer object
currentLayer.rectangle = CreateRectangle(cellValue);
//store Top & Rectangle object in GlassRectangle class which is referenced in MaterialLayer class
currentLayer.GlassRectangle.Rectangle = currentLayer.rectangle;
currentLayer.GlassRectangle.Top = canvasTop;
}
}
}
This create rectangle one after other like Stacked item on canvas. But when i modify the value of Thickness column which is Height of Rectangle it reflects on canvas but the Other Rectangle below must appear after the Changed Height of current rectangle.
Note: I cant use WrapPanel in my application. Just to modify existing code using Canvas.
Help Appreciated!
Modified For Loop in CellChange Event:
int layerIndex = materialBindlist.IndexOf(currentLayer);
for(int i = layerIndex; i < materialBindlist.Count-1; i++)
{
//set the top position of all other rectangles that are below selected rectangle/row
//(Current-Old)+Top
double top=Convert.ToDouble((currentHeight - oldHeight) + materialBindlist[i + 1].GlassRectangle.Top);
Canvas.SetTop(materialBindlist[i + 1].rectangle,top);
materialBindlist[i + 1].GlassRectangle.Top = top;
}
What you're looking for can be done even with a Canvas however you should really consider using something like an ItemsControl for this.
Solution when forced to use Canvas:
private void Refresh() {
for (int i = 1; i < canvasboard.Children.Count; ++i) {
var currentElement = canvasboard.Children[i] as FrameworkElement;
var previousElement = canvasboard.Children[i - 1] as FrameworkElement;
if (currentElement == null || previousElement == null)
return;
var requiredTop = Canvas.GetTop(previousElement) + previousElement.Height - 1;
if (Math.Abs(Canvas.GetTop(currentElement) - requiredTop) > 0.0)
Canvas.SetTop(currentElement, requiredTop);
}
}
Now call this function "after" you change the size of an existing element in the Canvas and it will re-position the elements accordingly to suit the new dimension. In your code, it would be called from the gridInner_CellValueChanged(...) function after you set the new height in "edit" mode.
What you should try to do:
If your able to persuade whoever you need to and get to use something like an ItemsControl, this will be so much simpler.
say a rough example:
xaml could be:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
with Items declared as public ObservableCollection<Rectangle> Items { get; set; } in code.
Now your Add() function could just be:
private void Add() {
var rect = new Rectangle {
Stroke = Brushes.Red,
StrokeThickness = 1,
Height = Convert.ToDouble(txtheight.Text),
Width = 100
};
Items.Add(rect);
}
and as for updates when your editing existing control's that would be automatic in this case. There is no hard-coded positioning as the Layout container takes care of all that mess for you.
You can ofcourse switch the Items collection type to your own custom control type MyLayer and with it implementing INPC, changes would still continue to be automatic. You'd have to define a DataTemplate now to get your Item to be rendered but that's like 3 lines of work in just xaml.
You can also just work of the Items property directly when needing to tweak an exisiting control than having to reference the ItemsControl in code-behind. Binding's should take care of the updates to the view automatically.
Modified for loop in Cell Change Event:
int layerIndex = materialBindlist.IndexOf(currentLayer);
for(int i = layerIndex; i < materialBindlist.Count-1; i++)
{
//set the top position of all other rectangles that are below selected rectangle/row
//(Current-Old)+Top
double top=Convert.ToDouble((currentHeight - oldHeight) + materialBindlist[i + 1].GlassRectangle.Top);
Canvas.SetTop(materialBindlist[i + 1].rectangle,top);
materialBindlist[i + 1].GlassRectangle.Top = top;
}
it works now..!Thanks to all!
I'm trying to create a custom Panel control with my own layout engine.
I need every control that is added to my panel to be added below and to take full width (-padding), like below:
Below is my code:
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Layout;
namespace VContainer
{
internal class VerticalFillList : Panel
{
public VerticalFillList()
{
AutoScroll = true;
MinimumSize = new Size(200, 200);
Size = new Size(200, 300);
Padding = new Padding(10);
}
private readonly VerticalFillLayout _layoutEngine = new VerticalFillLayout();
public override LayoutEngine LayoutEngine
{
get { return _layoutEngine; }
}
private int _space = 10;
public int Space
{
get { return _space; }
set
{
_space = value;
Invalidate();
}
}
}
internal class VerticalFillLayout : LayoutEngine
{
public override bool Layout(object container, LayoutEventArgs layoutEventArgs)
{
var parent = container as VerticalFillList;
Rectangle parentDisplayRectangle = parent.DisplayRectangle;
Point nextControlLocation = parentDisplayRectangle.Location;
foreach (Control c in parent.Controls)
{
if (!c.Visible)
{
continue;
}
c.Location = nextControlLocation;
c.Width = parentDisplayRectangle.Width;
nextControlLocation.Offset(0, c.Height + parent.Space);
}
return false;
}
}
}
Above code works fine, except one thing:
when I'm adding controls to my container they are added correctly (new below parent, 100% width), but when height of controls is bigger than my container height I get horizontal scrollbars, but after adding couple controls more scrollbar is removed.
Same thing happens when I want to resize my container:
How this can be fixed? I just need to remove that horizontal scrollbar.
Of course all improvements are welcome :)
I don't want to use table layout or flow layout as this one gives me exactly when I need.
I need a simple container that orders all child controls from top to bottom and stretches them horizontally so they take as much width so container horizontal scrollbar isn't needed.
Here is a working example that unfortunately, does not use your Layout Engine class. It simply relies on the OnControlAdded and OnControlRemoved methods, and anchoring and setting the AutoScrollMinSize property to specifically make sure the horizontal scrollbar never appears:
internal class VerticalPanel : Panel {
private int space = 10;
public int Space {
get { return space; }
set {
space = value;
LayoutControls();
}
}
protected override void OnControlAdded(ControlEventArgs e) {
base.OnControlAdded(e);
LayoutControls();
}
protected override void OnControlRemoved(ControlEventArgs e) {
base.OnControlRemoved(e);
LayoutControls();
}
private void LayoutControls() {
int height = space;
foreach (Control c in base.Controls) {
height += c.Height + space;
}
base.AutoScrollMinSize = new Size(0, height);
int top = base.AutoScrollPosition.Y + space;
int width = base.ClientSize.Width - (space * 2);
foreach (Control c in base.Controls) {
c.SetBounds(space, top, width, c.Height);
c.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
top += c.Height + space;
}
}
}
You can set the AnchorProperty at you Buttons like:
button1.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
So they'll be resized horizontally
I'm trying to make a custom ContentControl that takes on the shape of a Polyogon with rounded corners. For some reason when I set the Clip property on the Control, nothing shows up. Any help is appreciated...
PageHost.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls;
namespace DtcInvoicer.Controls
{
public class PageHost:UserControl
{
#region public ImageSource Icon;
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(PageHost));
public ImageSource Icon
{
get { return GetValue(IconProperty) as ImageSource; }
set { SetValue(IconProperty, value); }
}
#endregion
#region public string Title;
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(PageHost));
public string Title
{
get { return GetValue(TitleProperty).ToString(); }
set { SetValue(TitleProperty, value); }
}
#endregion
#region public double Radius;
public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(PageHost));
public double Radius
{
get { return (double)GetValue(RadiusProperty); }
set
{
SetValue(RadiusProperty, value);
DoClip();
}
}
#endregion
public PageHost()
{
Loaded += new RoutedEventHandler(PageHost_Loaded);
SizeChanged += new SizeChangedEventHandler(PageHost_SizeChanged);
}
#region Event Handlers
private void PageHost_Loaded(object sender, RoutedEventArgs e)
{
DoClip();
}
private void PageHost_SizeChanged(object sender, SizeChangedEventArgs e)
{
DoClip();
}
#endregion
private void DoClip()
{
Polygon p = new Polygon()
{
Points = new PointCollection()
{
new Point(0, 0),
new Point(ActualWidth - 30, 0),
new Point(ActualWidth, 30),
new Point(ActualWidth, ActualHeight),
new Point(0, ActualHeight)
}
};
Geometry g1 = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight), Radius, Radius);
Geometry g2 = p.RenderedGeometry;
// Clip = g1; this works, the control shows up with the rounded corners
// Clip = g2; this does not work, nothing shows up
// this is what I want to do, I want to combine the two geometries
// but this does not work either
Clip = new CombinedGeometry(GeometryCombineMode.Intersect, g1, g2);
}
}
}
HomePage.xaml
<control:PageHost x:Class="DtcInvoicer.Pages.HomePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:control="clr-namespace:DtcInvoicer.Controls"
Width="500" Height="250" Radius="20" Background="Aqua">
</control:PageHost>
Setting the clip to a RenderedGeometry fails in this case because the RenderedGeometry has not yet been actually rendered, and is thus not available. For regular geometries, use this in DoClip:
Dispatcher.BeginInvoke(DispatcherPriority.Background, new ThreadStart(delegate
{
Clip = new CombinedGeometry(GeometryCombineMode.Intersect, g1, g2);
}));
With your RenderedGeometry, you would need to add it to the visual tree somewhere and then use its Loaded event before you set the Clip region, which would be hard. Try using a regular Geometry instead of a RenderedGeometry, with the same points, such as A path geometry. Here's an example of where I draw a triangle using a PathGeometry:
double leftPoint = cellRect.Right - 12;
if (leftPoint < cellRect.Left)
leftPoint = cellRect.Left;
double topPoint = cellRect.Top + (cellRect.Height - 4.0) / 2;
if (topPoint < cellRect.Top)
topPoint = cellRect.Top;
double rightPoint = leftPoint + 7;
if (rightPoint > cellRect.Right)
rightPoint = cellRect.Right;
double bottomPoint = topPoint + 4;
if (bottomPoint > cellRect.Bottom)
bottomPoint = cellRect.Bottom;
double middlePoint = leftPoint + 3;
if (middlePoint > cellRect.Right)
middlePoint = cellRect.Right;
PathFigure figure = new PathFigure();
figure.StartPoint = new Point(middlePoint, bottomPoint);
PathFigureCollection figCollection = new PathFigureCollection();
figCollection.Add(figure);
PathSegmentCollection segCollection = new PathSegmentCollection();
LineSegment topSeg = new LineSegment();
topSeg.Point = new Point(rightPoint, topPoint);
segCollection.Add(topSeg);
LineSegment midRightSeg = new LineSegment();
midRightSeg.Point = new Point(leftPoint, topPoint);
segCollection.Add(midRightSeg);
LineSegment midLeftSeg = new LineSegment();
midLeftSeg.Point = new Point(middlePoint+1, bottomPoint);
segCollection.Add(midLeftSeg);
figure.Segments = segCollection;
PathGeometry geo = new PathGeometry();
geo.Figures = figCollection;