how to remove calendaritem borders in wpf - wpf

I am using calendar in WPF and I want to remove the outer white border of it
(https://i.stack.imgur.com/HpTRL.jpg)
I need a calendar iteenter image description herem without borders

That calendar looks a bit odd with a grey background so I'm not totally sure what template you're using.
I would first try just setting the calendar border transparent. That seems so obvious maybe you tried it though.
<Calendar BorderBrush="Transparent"
x:Name="MyCalendar"
/>
If that does not work then you could potentially replace the entire template. That could be quite a lot of work if you have a theme.
Alternatively, you could change it in code.
Handle contentrendered or another event where you're sure the calendar will have been rendered.
Brute force iterate through all borders in the calendar setting them transparent:
private void Window_ContentRendered(object sender, EventArgs e)
{
var borders = FindVisualChildren<Border>(MyCalendar);
foreach (Border b in borders)
{
b.BorderBrush = Brushes.Transparent;
}
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject dO) where T : DependencyObject
{
if (dO == null) yield return (T)Enumerable.Empty<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dO); i++)
{
DependencyObject ithChild = VisualTreeHelper.GetChild(dO, i);
if (ithChild == null) continue;
if (ithChild is T t) yield return t;
foreach (T childOfChild in FindVisualChildren<T>(ithChild)) yield return childOfChild;
}
}
I think I might have originally got that findvisualchildren off some post on SO, not sure.

Related

TreeView: Place selection indicator along the item at the left edge of the control

So the requirement is simple, but the solution doesn't seem to be (or at least I haven't succeeded yet). I need to display a vertical bar at the left side of the currently selected item of the TreeView control. Something like this:
Problem I'm facing is that with child items, this indicator also moves towards right, as it is part of the ItemTemplate, like this:
This is undesirable. I need the red indicator to stick to the left edge of the control, like this:
I can see why this happens. The ItemsPresenter in TreeViewItem template introduces a left margin of 16 units, which causes the all child items to move right-wards as well. I can't figure out how to avoid it.
Note: The red bar is a Border with StrokeThickness set to 4,0,0,0. It encompasses the Image and TextBlock elements inside it, though this doesn't directly have anything to do with the problem.
As you are aware, since the left vacant space is outside of ItemsPresenter which hosts the content of TreeViewItem, you cannot accomplish it by ordinary Style.
Instead, a workaround would be to change the bar to an element such as Rentangle and move it to the edge of TreeView. For example, it can be done by an attached property which is to be attached to the element and move it to the edge of TreeView with a specified left margin.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
public static class TreeViewHelper
{
public static double? GetLeftMargin(DependencyObject obj)
{
return (double?)obj.GetValue(LeftMarginProperty);
}
public static void SetLeftMargin(DependencyObject obj, double value)
{
obj.SetValue(LeftMarginProperty, value);
}
public static readonly DependencyProperty LeftMarginProperty =
DependencyProperty.RegisterAttached("LeftMargin", typeof(double?), typeof(TreeViewHelper), new PropertyMetadata(null, OnValueChanged));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((d is FrameworkElement element) && (e.NewValue is double leftMargin))
{
element.Loaded += (_, _) =>
{
TreeView? tv = GetTreeView(element);
if (tv is null)
return;
Point relativePosition = element.TransformToAncestor(tv).Transform(new Point(0, 0));
element.RenderTransform = new TranslateTransform(leftMargin - relativePosition.X, 0);
};
}
}
private static TreeView? GetTreeView(FrameworkElement element)
{
DependencyObject test = element;
while (test is not null)
{
test = VisualTreeHelper.GetParent(test);
if (test is TreeView tv)
return tv;
}
return null;
}
}
Edit:
This workaround does not depend on how to show/hide the bar upon selection of the ListViewItem. Although the question does not provide the actual code for this, if you implement a mechanism to change BorderBrush upon selelection, you can modify it to change Fill of the bar (in the case of Rectangle).

Stuck in an OnPaint loop with a custom control derived from a panel

I have a custom panel designed to be able to either show a solid background color, a gradient background color, or a picture.
I've overridden both the BackgroundImage property and the OnPaint method like so:
protected override void OnPaint(PaintEventArgs pe){
Rectangle rc = new Rectangle(0, 0, this.Width, this.Height);
if (this.DrawStyle != DrawStyle.Picture){
base.BackgroundImage = null;
if (this.DrawStyle == DrawStyle.Solid){
using (SolidBrush brush = new SolidBrush(this.PrimaryColor))
pe.Graphics.FillRectangle(brush, rc);
}
else if (this.DrawStyle == DrawStyle.Gradient){
using (LinearGradientBrush brush =
new LinearGradientBrush(
rc, this.PrimaryColor, this.SecondaryColor, this.Angle))
pe.Graphics.FillRectangle(brush, rc);
}
}
else if (this._BGImage != null)
base.BackgroundImage = this._BGImage;
}
public override Image BackgroundImage{
get { return this._BGImage; }
set { this._BGImage = value; }
}
The reason being is that the control must be flexible enough to change the background type (Solid, Gradient or Picture) during run time, so the control holds onto the set background image and presents it when necessary.
I am running into a problem, however.
If I do not set the background image, there is no problem. OnPaint is called about 5 times and then it goes fine.
However, when I DO set the background image, it seems to go crazy, calling OnPaint over and over and over and over and over and over again.
This clearly has something to do with overriding the background image and it's a problem, because it's hanging up and nothing on the panel is getting updated until I change the panel appearance.
So I guess the question I have is why is it getting stuck in this OnPaint loop when I set the background image?
Comment out this:
// base.BackgroundImage = this._BGImage;
It's causing it to recursively paint itself. You should never set properties in a paint routine.
Furthermore, you aren't accomplishing anything by overriding the BackgroundImage, and if you do override the property, that should be the only place where you assign the base.BackgroundImage value. Consider removing that.
I reworked your code to this:
protected override void OnPaintBackground(PaintEventArgs e) {
if (this.DrawStyle == DrawStyle.Picture) {
base.OnPaintBackground(e);
}
}
protected override void OnPaint(PaintEventArgs e) {
Rectangle rc = this.ClientRectangle;
if (this.DrawStyle == DrawStyle.Solid) {
using (SolidBrush brush = new SolidBrush(this.PrimaryColor))
e.Graphics.FillRectangle(brush, rc);
} else if (this.DrawStyle == DrawStyle.Gradient) {
using (LinearGradientBrush brush =
new LinearGradientBrush(
rc, this.PrimaryColor, this.SecondaryColor, this.Angle))
e.Graphics.FillRectangle(brush, rc);
}
base.OnPaint(e);
}
Make sure to add this.DoubleBuffered = true; and this.ResizeRedraw = true; in your panel's constructor to avoid unnecessary flicker.

Get the scroll position of a WPF TextBox

I need to add some decoration to the contents of a WPF TextBox control. That works fine basically, I can get the position of specified character indices and layout my other elements accordingly. But it all breaks when the TextBox is scrolled. My layout positions don't match with the displayed text anymore because it has moved elsewhere.
Now I'm pretty surprised that the TextBox class doesn't provide any information about its scrolling state, nor any events when the scrolling has changed. What can I do now?
I used Snoop to find out whether there is some scrolling sub-element that I could ask, but the ScrollContentPresenter also doesn't have any scrolling information available. I'd really like to put my decoration elements right into the scrolled area so that the scrolling can affect them, too, but there can only be a single content control and that's one of the TextBox internals already.
I'm not sure how to capture an event when the textbox has been scrolled (probably use narohi's answer for that), but there is a simple way to see what the current scroll position is:
// Gets or sets the vertical scroll position.
textBox.VerticalOffset
(From http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.textboxbase.verticaloffset(v=vs.100).aspx)
I'm using it to see if the textbox is scrolled to the end, like this:
public static bool IsScrolledToEnd(this TextBox textBox)
{
return textBox.VerticalOffset + textBox.ViewportHeight == textBox.ExtentHeight;
}
You can get the ScrollViewer with this method by passing in your textbox as the argument and the type ScrollView. Then you may subscribe to the ScrollChanged event.
public static T FindDescendant<T>(DependencyObject obj) where T : DependencyObject
{
if (obj == null) return default(T);
int numberChildren = VisualTreeHelper.GetChildrenCount(obj);
if (numberChildren == 0) return default(T);
for (int i = 0; i < numberChildren; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
{
return (T)(object)child;
}
}
for (int i = 0; i < numberChildren; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
var potentialMatch = FindDescendant<T>(child);
if (potentialMatch != default(T))
{
return potentialMatch;
}
}
return default(T);
}
Example:
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
ScrollViewer s = FindDescendant<ScrollViewer>(txtYourTextBox);
s.ScrollChanged += new ScrollChangedEventHandler(s_ScrollChanged);
}
void s_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
// check event args for information needed
}

First Listbox scrollbar movement should effect Second Listbox scrollbar movement? But How?

I have 4 List Boxes in my WPF App. Each of them, at any given point of time contains equal no. of String ListBoxItems in them. If selected index of any one of them changes the other three also reflect the same behaviour. What i want is that when a user moves scrollbar of one of them the other three should also move simultaneoulsly.
I tried Scrollintoview but it does not work bcoz if i select an item of a listBox and apply scrollintoview for other three Listboxes the selected item in them come on the top.
That's why i think scrollbar movement should be the best option for this. How to do that?
In XAML hook the ScrollChanged event
ScrollViewer.ScrollChanged="ListBox_ScrollChanged"
In CodeBehind find the Scrollviewers inside the ListBoxes and apply the Vertical offset:
private void ListBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var sourceScrollViewer = FindVisualChild<ScrollViewer>(sender as DependencyObject) as ScrollViewer;
var targetScrollViewer = FindVisualChild<ScrollViewer>(listBox2) as ScrollViewer;
targetScrollViewer.ScrollToVerticalOffset(sourceScrollViewer.VerticalOffset);
}
// helper method
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
Well in code it is something like that :
1) get the four scrollviewers of the four ListViews
( by finding them within the child (VisualTreeHelper.getchild)
inside a method like FindDescendant(...))
2) hook their scrolls events (ScrollChanged) to a common sub that
will get the VerticalOffset of the one that triggered the event
and ScrollToVerticalOffset(.) the others.
must be possible in xaml also, but seems more complicated to me.

How can I show scrollbars on a System.Windows.Forms.TextBox only when the text doesn't fit?

For a System.Windows.Forms.TextBox with Multiline=True, I'd like to only show the scrollbars when the text doesn't fit.
This is a readonly textbox used only for display. It's a TextBox so that users can copy the text out. Is there anything built-in to support auto show of scrollbars? If not, should I be using a different control? Or do I need to hook TextChanged and manually check for overflow (if so, how to tell if the text fits?)
Not having any luck with various combinations of WordWrap and Scrollbars settings. I'd like to have no scrollbars initially and have each appear dynamically only if the text doesn't fit in the given direction.
#nobugz, thanks, that works when WordWrap is disabled. I'd prefer not to disable wordwrap, but it's the lesser of two evils.
#André Neves, good point, and I would go that way if it was user-editable. I agree that consistency is the cardinal rule for UI intuitiveness.
I came across this question when I wanted to solve the same problem.
The easiest way to do it is to change to System.Windows.Forms.RichTextBox. The ScrollBars property in this case can be left to the default value of RichTextBoxScrollBars.Both, which indicates "Display both a horizontal and a vertical scroll bar when needed." It would be nice if this functionality were provided on TextBox.
Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form. It's not quite perfect but ought to work for you.
using System;
using System.Drawing;
using System.Windows.Forms;
public class MyTextBox : TextBox {
private bool mScrollbars;
public MyTextBox() {
this.Multiline = true;
this.ReadOnly = true;
}
private void checkForScrollbars() {
bool scroll = false;
int cnt = this.Lines.Length;
if (cnt > 1) {
int pos0 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(0)).Y;
if (pos0 >= 32768) pos0 -= 65536;
int pos1 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(1)).Y;
if (pos1 >= 32768) pos1 -= 65536;
int h = pos1 - pos0;
scroll = cnt * h > (this.ClientSize.Height - 6); // 6 = padding
}
if (scroll != mScrollbars) {
mScrollbars = scroll;
this.ScrollBars = scroll ? ScrollBars.Vertical : ScrollBars.None;
}
}
protected override void OnTextChanged(EventArgs e) {
checkForScrollbars();
base.OnTextChanged(e);
}
protected override void OnClientSizeChanged(EventArgs e) {
checkForScrollbars();
base.OnClientSizeChanged(e);
}
}
I also made some experiments, and found that the vertical bar will always show if you enable it, and the horizontal bar always shows as long as it's enabled and WordWrap == false.
I think you're not going to get exactly what you want here. However, I believe that users would like better Windows' default behavior than the one you're trying to force. If I were using your app, I probably would be bothered if my textbox real-estate suddenly shrinked just because it needs to accomodate an unexpected scrollbar because I gave it too much text!
Perhaps it would be a good idea just to let your application follow Windows' look and feel.
There's an extremely subtle bug in nobugz's solution that results in a heap corruption, but only if you're using AppendText() to update the TextBox.
Setting the ScrollBars property from OnTextChanged will cause the Win32 window (handle) to be destroyed and recreated. But OnTextChanged is called from the bowels of the Win32 edit control (EditML_InsertText), which immediately thereafter expects the internal state of that Win32 edit control to be unchanged. Unfortunately, since the window is recreated, that internal state has been freed by the OS, resulting in an access violation.
So the moral of the story is: don't use AppendText() if you're going to use nobugz's solution.
I had some success with the code below.
public partial class MyTextBox : TextBox
{
private bool mShowScrollBar = false;
public MyTextBox()
{
InitializeComponent();
checkForScrollbars();
}
private void checkForScrollbars()
{
bool showScrollBar = false;
int padding = (this.BorderStyle == BorderStyle.Fixed3D) ? 14 : 10;
using (Graphics g = this.CreateGraphics())
{
// Calcualte the size of the text area.
SizeF textArea = g.MeasureString(this.Text,
this.Font,
this.Bounds.Width - padding);
if (this.Text.EndsWith(Environment.NewLine))
{
// Include the height of a trailing new line in the height calculation
textArea.Height += g.MeasureString("A", this.Font).Height;
}
// Show the vertical ScrollBar if the text area
// is taller than the control.
showScrollBar = (Math.Ceiling(textArea.Height) >= (this.Bounds.Height - padding));
if (showScrollBar != mShowScrollBar)
{
mShowScrollBar = showScrollBar;
this.ScrollBars = showScrollBar ? ScrollBars.Vertical : ScrollBars.None;
}
}
}
protected override void OnTextChanged(EventArgs e)
{
checkForScrollbars();
base.OnTextChanged(e);
}
protected override void OnResize(EventArgs e)
{
checkForScrollbars();
base.OnResize(e);
}
}
What Aidan describes is almost exactly the UI scenario I am facing. As the text box is read only, I don't need it to respond to TextChanged. And I'd prefer the auto-scroll recalculation to be delayed so it's not firing dozens of times per second while a window is being resized.
For most UIs, text boxes with both vertical and horizontal scroll bars are, well, evil, so I'm only interested in vertical scroll bars here.
I also found that MeasureString produced a height that was actually bigger than what was required. Using the text box's PreferredHeight with no border as the line height gives a better result.
The following seems to work pretty well, with or without a border, and it works with WordWrap on.
Simply call AutoScrollVertically() when you need it, and optionally specify recalculateOnResize.
public class TextBoxAutoScroll : TextBox
{
public void AutoScrollVertically(bool recalculateOnResize = false)
{
SuspendLayout();
if (recalculateOnResize)
{
Resize -= OnResize;
Resize += OnResize;
}
float linesHeight = 0;
var borderStyle = BorderStyle;
BorderStyle = BorderStyle.None;
int textHeight = PreferredHeight;
try
{
using (var graphics = CreateGraphics())
{
foreach (var text in Lines)
{
var textArea = graphics.MeasureString(text, Font);
if (textArea.Width < Width)
linesHeight += textHeight;
else
{
var numLines = (float)Math.Ceiling(textArea.Width / Width);
linesHeight += textHeight * numLines;
}
}
}
if (linesHeight > Height)
ScrollBars = ScrollBars.Vertical;
else
ScrollBars = ScrollBars.None;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
finally
{
BorderStyle = borderStyle;
ResumeLayout();
}
}
private void OnResize(object sender, EventArgs e)
{
m_timerResize.Stop();
m_timerResize.Tick -= OnDelayedResize;
m_timerResize.Tick += OnDelayedResize;
m_timerResize.Interval = 475;
m_timerResize.Start();
}
Timer m_timerResize = new Timer();
private void OnDelayedResize(object sender, EventArgs e)
{
m_timerResize.Stop();
Resize -= OnResize;
AutoScrollVertically();
Resize += OnResize;
}
}

Resources