I have a TabControl with many TabItems inside.
I am trying to make TabControl's width equal to Max of its TabItems Width(same for Height).
I cannot set it explicitly, because my program is multilanguage, and so depending on language selected, I need different widths to all of my labels(they all are in "auto").
Is there a way to do in only in XAML?
Edit :
I found a part of solution in order to do it in the code : When I open my window I do the following :
List<TabItem> list = new List<TabItem>();
list.AddRange(tabControl.Items.OfType<TabItem>());
foreach (TabItem tabItem in list)
{
tabControl.SelectedItem=tabItem;
tabControl.UpdateLayout();
tabItem.UpdateLayout();
System.Threading.Thread.Sleep(250);
maxWidth = Math.Max(maxWidth, tabItem.ActualWidth);
maxHeight = Math.Max(maxHeight, tabItem.ActualHeight);
}
tabControl.Width = maxWidth;
tabControl.Height = maxHeight;
It seems to be a working solution, but I don't understand why tabItem.ActualWidth and tabItem.ActualHeight are always set to 0?
To understand better, I put a screenshot of my window :
When I open my window, I already know in which language it may be opened, so the TabItems dimensions don't change after the windows is loaded.
Edit 2 :
Here is concretely what I mean by "size of TabItems differ regarding used language, this is not the best example as here difference is small, but I would like to apply that to all TabContols of my project to be sure I never met the problem if I make changes in future, or even add another languages :
In Russian :
In English :
I finally found a solution, maybe not the best, but it is working perfectly, and didn't find any other solution.
It is not possible to do the job when initializing the window, as the contents has not been loaded yet (MyWindow.Show()), so I had to use the SizeChanged event of TabControl.
On my ViewModel I set two parameters TabControlWidth and TabControlHeight.
private double tabControlWidth = 0;
public double TabControlWidth
{
get { return tabControlWidth; }
set
{
if (this.tabControlWidth != value)
{
tabControlWidth = value;
this.NotifyPropertyChanged("TabControlWidth");
}
}
}
private double tabControlHeight = 0;
public double TabControlHeight
{
get { return tabControlHeight; }
set
{
if (this.tabControlHeight != value)
{
tabControlHeight = value;
this.NotifyPropertyChanged("TabControlHeight");
}
}
}
in my file MyWindow.xaml.cs I add a bool property to check when I checked all TabItems(obviously, this property is set to false when initializing the window) :
bool allTabItemsChecked { get; set; }
Finally, I need to add to my TabControl two things :
Bind Width and Height to my properties in ViewModel.
Add event SizeChanged
SizeChanged="TabControlSizeChanged" MinHeight="{Binding TabControlHeight}" MinWidth="{Binding TabControlWidth}"
(excuse me, I don't manage to display the last line as code? often happens after I use "-" or "*")
Finally I make the SizeChanged function as following :
private void TabControlSizeChanged(object sender, SizeChangedEventArgs e)
{
if(this.allTabItemsChecked==false)
{
int index = tabControl.SelectedIndex;
List<TabItem> list = new List<TabItem>();
list.AddRange(tabControl.Items.OfType<TabItem>());
if (index < list.Count()-1)
{
tabControl.SelectedIndex = index + 1;
}
else
{
this.allTabItemsChecked = true;
tabControl.SelectedIndex = 0;
}
contexte.TabControlWidth = Math.Max(tabControl.ActualWidth, contexte.TabControlWidth);
contexte.TabControlHeight = Math.Max(tabControl.ActualHeight, contexte.TabControlHeight);
}
}
When we display the window, event is automatically launched.
I will then change TabControl.SelectedIndex to the next TabItem, and update TabControl dimensions to the bigger one.
When I reached the last index, I just set SelectedIndex to 0, then set allTabItemsChecked to true.
Related
I am working on a WPF application and i have a textbox bound (bidirectionally) to a property in my view model.
I am trying to prevent a user from typing more than 100 characters into this textbox (this is the max the database will store) so i have written this.
public abstract class AppBaseViewModel : ViewModelBase
{
private String _text;
public String Text
{
get { return _text; }
set
{
_text = CheckTextLength(value, _text);
OnPropertyChanged("Text");
}
}
private string CheckTextLength(string value, string text)
{
if (value.Length < 100)
{
return value;
}
else
{
return text;
}
}
}
All this code seems to do is save the first 100 characters to the field but it still allows the user to carry on typing past 100 characters... i would guess it is because the field value isn't being passed back to the textbox.
I don't understand why this doesn't work as i did something similar using MVVM Light's RaisePropertyChange() in a different application.
It is worth noting that i am unable to access the designer for the textbox so cannot set the .Net textbox property for max length.
Edit: Just for clarification i cannot view or edit the xaml as some are suggesting as i do not have access to the XAML file (i know, it's stupid). All the bindings we use are two way by default
Have you tried with TextBox.MaxLength ?
<TextBox MaxLength="100"/>
Gets or sets the maximum number of characters that can be manually entered into the text box.
If no access to the XAML, eventually get access to the XAML instead of parsing and verifying lengths of arrays and use substrings here and there. At least that's what i would do for this simple issue or talk to the designer to add that small piece of code.
Update 1
public static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
Go and get that child and set its MaxLength. This is just a slight modification on the View so it will not affect the MVVM pattern.
OK. I'm not at all sure that I'm proud of this, but am presenting it as an alternative.
You can change the UpdateSourceTrigger of the TextBox's Text property by applying a universal Style to all of the TextBoxes. This is only going to be practical in pretty weird arrangements, but the question is a little unusual in itself.
XAML codebehind:
//I'm using MVVM Light here - you need to be able to find an instance
//of your AppBaseViewModel somehow.
private ViewModelLocator _locator;
//View codebehind constructor, may need to change names as appropriate
public AppBaseView()
{
InitializeComponent();
//MVVM Light again
_locator = new ViewModelLocator();
//Create the binding
Binding binding = new Binding();
//Source = The instance of your ViewModel
binding.Source = _locator.AppBaseViewModel ;
binding.Path = new PropertyPath("Text");
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
//Create a Style with no Key - this will apply to *all* TextBoxes
//without their own explicit Style set.
Style style = new Style(typeof(TextBox));
style.Setters.Add(new Setter(TextBox.TextProperty, binding));
//Add the Style to the XAML's Resources:
Resources.Add(typeof(TextBox), style);
}
The view won't listen to the PropertyChanged notification if it's currently trying to change the property itself.
The only thing that comes to mind is launching an extra delayed PropertyChanged notification when you detect the constraint is not met...
private string CheckTextLength(string value, string text)
{
if (value.Length < 100)
{
return value;
}
else
{
MyDispatcher.BeginInvoke(new Action(() =>
OnPropertyChanged("Text")),
DispatcherPriority.Loaded);
return text;
}
}
Can't try the code, so sorry if it doesn't build righ away. MyDispatcher could be your Application.Current.Dispatcher, for instance.
The xaml view /the binding is only updated when the textbox has lost focus. if the text entered is <100 then the value is set otherwise _text is set. this means that initially _text has no value so null will be set upon the if statement being false. i also suggest yo use RaisePropertyChanged(); and when used within the property itself no parameter is needed.
I am trying to implement a way to move back the scroll bar of a datagrid back to a previous position following this sample http://www.codeproject.com/Articles/109531/Controlling-and-Viewing-the-ScrollBar-Positions-of but this method always returns null making it impossible to create automation. Does any have an idea why this would always return null?
public static IScrollProvider GetScrollProvider(DataGrid grid)
{
var p = FrameworkElementAutomationPeer.FromElement(grid)
?? FrameworkElementAutomationPeer.CreatePeerForElement(grid);
return p.GetPattern(PatternInterface.Scroll) as IScrollProvider;
}
Ancient history, but at least current version seems to work liek charm in all cases.
//p.GetPattern(PatternInterface.Scroll) as IScrollProvider;
{System.Windows.Automation.Peers.DataGridAutomationPeer}
[System.Windows.Automation.Peers.DataGridAutomationPeer]:
{System.Windows.Automation.Peers.DataGridAutomationPeer}
HorizontallyScrollable: true
HorizontalScrollPercent: 0.0
HorizontalViewSize: 81.44329896907216
VerticallyScrollable: true
VerticalScrollPercent: 29.062733871459486
VerticalViewSize: 2.9625
I know its a old post, but in case someone gets here with the same problem, (Like me), here is how i solved it:
For some reason, when you the the scrollbar of the DataGrid, if the user didn't scroll yet, it will inform that the maximum position is 0, even if it isn't. So i saved the maximum value before updating and restore it after:
First i create a class to save those values. It could be variables, but a class seemed more organized:
public class ScrollInfo
{
public double HorizontalPosition;
public double HorizontalMaximum;
public double VerticalPosition;
public double VerticalMaximum;
}
Before i update the ItemSource property i use this method to save Scroll info:
public static ScrollInfo GetScrollInfo(this DataGrid grid)
{
ScrollInfo oInfo = new ScrollInfo();
ScrollBar sbHorizontal = grid.GetScrollbar(ScrollMode.Horizontal);
oInfo.HorizontalMaximum = sbHorizontal.Maximum;
oInfo.HorizontalPosition = sbHorizontal.Value;
ScrollBar sbVertical = grid.GetScrollbar(ScrollMode.Vertical);
oInfo.VerticalMaximum = sbVertical.Maximum;
oInfo.VerticalPosition = sbVertical.Value;
return oInfo;
}
And then i call this method to set this info back to the grid after the update:
public static void SetScrollPosition(this DataGrid grid, ScrollInfo info)
{
if (info.HorizontalPosition > 0)
{
ScrollBar sbHorizontal = grid.GetScrollbar(ScrollMode.Horizontal);
sbHorizontal.Maximum = info.HorizontalMaximum;
grid.Scroll(ScrollMode.Horizontal, info.HorizontalPosition);
}
if (info.VerticalPosition > 0)
{
ScrollBar sbVertical = grid.GetScrollbar(ScrollMode.Vertical);
sbVertical.Maximum = info.VerticalMaximum;
grid.Scroll(ScrollMode.Vertical, info.VerticalPosition);
}
}
I even create a UpdateItemSource method to make things easier:
public static void UpdateItemSource(this DataGrid grid, IEnumerable itemSource)
{
ScrollInfo oInfo = grid.GetScrollInfo();
grid.ItemsSource = itemSource;
grid.SetScrollPosition(oInfo);
}
Other methods I use, wich i guess i take from the same post the user who post the question did:
public static void Scroll(this DataGrid grid, ScrollMode mode, double position)
{
// Get the scrollbar and convert the position to percent.
var scrollBar = grid.GetScrollbar(mode);
double positionPct = ((position / scrollBar.Maximum) * 100);
// Scroll to a specfic percentage of the scrollbar.
grid.ScrollToPercent(mode, positionPct);
}
public static void ScrollToPercent(this DataGrid grid, ScrollMode mode, double percent)
{
// Fix the percentage.
if (percent < 0)
percent = 0;
else if (percent > 100)
percent = 100;
// Get the scroll provider.
var scrollProvider = GetScrollProvider(grid);
// Scroll.
switch (mode)
{
case ScrollMode.Vertical:
scrollProvider.SetScrollPercent( System.Windows.Automation.ScrollPatternIdentifiers.NoScroll, percent);
break;
case ScrollMode.Horizontal:
scrollProvider.SetScrollPercent(percent, System.Windows.Automation.ScrollPatternIdentifiers.NoScroll);
break;
}
}
My tablelayout panel has one column and three rows. (one docked to Fill panel in each cell.)
Now I would like to be able to hide/show the rows . I want only one row to be visible at any time ( based on a user selection of some radio buttons) and I want to to get resized so it fills all the area of the TableLayoutPanel.
How can I do that? Any thoughts?
If rows in your TableLayoutPanel is autosized then hiding content panel will hide cell where panel placed too.
I would suggest setting the other rows heights to 0 is the easiest way:
Row one:
this.tableLayoutPanel1.RowStyles[1].Height = 0;
Try this
TableLayoutPanel1.ColumnStyles[1].SizeType = SizeType.Absolute;
TableLayoutPanel1.ColumnStyles[1].Width = 0;
So why did you use a TableLayoutPanel?
Just put three Panels on your form, fill in everyone the content of each row and set the Dock property of all three panels to Fill. Set two panels Visible = false and one to true.
If you like to see another panel, just make it visible and hide the other two (based on your radio button settings).
My scenario is similar. I needed a TableLayoutPanel with 4 rows each of which needed to be visible according to a checkbox selection. So instead of only showing one row at a time, I can show 1 - 4.
After designing the layout with 1 column and 4 rows, the controls were added and Dock set to Fill for each one.
Then in a single CheckedChanged event handler for the checkboxes, I coded as shown below. It's kind of a brute force method, but, Hey...it works!
private void checkBox_CheckedChanged(object sender, EventArgs e)
{
this.SuspendLayout();
int seldCount = checkBox1.Checked ? 1 : 0;
seldCount += checkBox2.Checked ? 1 : 0;
seldCount += checkBox3.Checked ? 1 : 0;
seldCount += checkBox4.Checked ? 1 : 0;
float pcnt = 0;
if (seldCount == 1)
pcnt = 1;
if (seldCount == 2)
pcnt = 0.5f;
if (seldCount == 3)
pcnt = 0.33f;
if (seldCount == 4)
pcnt = 0.25f;
int newHeight = (int)(tableLayoutPanel1.Height * pcnt);
if (checkBox1.Checked)
{
tableLayoutPanel1.RowStyles[0].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[0].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[0].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[0].Height = 0;
}
if (checkBox2.Checked)
{
tableLayoutPanel1.RowStyles[1].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[1].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[1].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[1].Height = 0;
}
if (checkBox3.Checked)
{
tableLayoutPanel1.RowStyles[2].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[2].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[2].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[2].Height = 0;
}
if (checkBox4.Checked)
{
tableLayoutPanel1.RowStyles[3].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[3].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[3].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[3].Height = 0;
}
this.ResumeLayout();
}
To hide row try this!!
tableLayoutPanel1.RowStyles[1].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[1].Height = 0;
I had similar task to do and my solution is following:
Add a TableLayoutPanel to your form (or any container).
Set TableLayoutPanel's columns and rows count to 1 and size to 100%.
Set Dock to Fill.
Set GrowStyle to fixedSize.
Set AutoSize to true.
Then programmatically add all of three forms/controls, one of which you have to show depending on radio button choice. Be sure that only one of them is visible. That could be done with initial FirstControl.Show(); and then on each RadioButton event hide the current one and show another. you may "remember" in local variable (say: "currentlyVisibleControl" the reference which is currently visible)
note: if you will .Show() more than one at time. then TableLayoutPanel wil fire the exception that it is full and can't add any more item.
P.S. In My own example I have TableLayoutPanel in MDI window and three forms which substitute each other on button clicks on them so I think copying my source code will complicate the "verbal" example.
P.P.S. From my experience Visual Studio does some weird things in design mode sometimes. I had to remove and re-add the TableLayoutPanel to set properties correctly and get the results both in designer and in runtime. So if either autosize or absolute/percent values are not depicted on designer screen it may be designers problem rather that yours. JUST DELETE IT AND RETRY.
I tried fooling around with the Height and SizeType properties, but it was giving me odd results. For example, the Labels on the target row were being hidden, but the TextBoxes were not.
Here is an extension class that I came up with using #arbiter's suggestion of hiding the children Controls of the row.
// these methods only works on rows that are set to AutoSize
public static class TableLayoutPanelExtensions
{
public static void HideRows(this TableLayoutPanel panel, params int[] rowNumbers)
{
foreach (Control c in panel.Controls)
{
if (rowNumbers.Contains(panel.GetRow(c)))
c.Visible = false;
}
}
public static void ShowRows(this TableLayoutPanel panel, params int[] rowNumbers)
{
foreach (Control c in panel.Controls)
{
if (rowNumbers.Contains(panel.GetRow(c)))
c.Visible = true;
}
}
}
I'm looking for a function which get for input tabcontrol and tabpage index and will return if the tabPage header is shown or not (in case that there are extra pages and scrollbars appear, some of tabPages headers can be disappeared).
does someone have a code for it?
check the following code works on winForms, you can increase or decrease the scrollbarwidth variable to fit your opinion on how much size you will consider the page shown or not.
private bool TabPageShown(TabControl tabCtrl, int tabIndex)
{
Rectangle rct = tabCtrl.GetTabRect(tabIndex);
int scrollBarWidth = 24;
if (rct.X - scrollBarWidth < tabCtrl.Width)
{
return true;
}
else
{
return false;
}
}
Something strange is going on with ObservableCollection.
I have the following code:
private readonly ObservableCollection<DisplayVerse> _display;
private readonly ListBox _box;
private void TransferToDisplay()
{
double elementsHeight = 0;
_display.Clear();
for (int i = 0; i < _source.Count; i++) {
DisplayVerse verse = _source[i];
_display.Add(verse);
elementsHeight += CalculateItemsHeight(i);
if (elementsHeight + Offset > _box.ActualHeight) {
_display.RemoveAt(_display.Count - 1);
break;
}
}
MessageBox.Show(elementsHeight.ToString());
}
private double CalculateItemsHeight(int index)
{
ListBoxItem lbi = _box.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem;
return lbi != null ? lbi.ActualHeight : 0;
}
What I am trying to do here is control how many items go into the ObservableCollection _display. Now, within this for loop you can see that elements are added until the total elements height (+offset) is greater than the listbox itself.
Now, this is strange, the elementsHeight equals 0 after this for loop. (CalculateItemsHeight returns 0 in all for loop iterations even though the lbi is not null) It seems that the UI elements defined in the datatemplate are not created...
Yet.
Now, if I put some MessageBoxes after the _display.Add(verse) you can see that the CalculateItemsHeight actually returns the height of an item.
for (int i = 0; i < _source.Count; i++) {
DisplayVerse verse = _source[i];
_display.Add(verse);
MessageBox.Show("pause"); // <----- PROBLEM?
elementsHeight += CalculateItemsHeight(i);
if (elementsHeight + Offset > _box.ActualHeight) {
_display.RemoveAt(_display.Count - 1);
break;
}
}
MessageBox.Show(elementsHeight.ToString());
After I modify the for loop as shown, the last MessageBox actually shows the actual height for all processed elements.
My question is - when are the UI elements actually created? It seems that it was done somewhere during the MessageBox display. This behaviour is pretty strange for me, maybe it has something to do with threading, not sure.
Adding to the _display ObservableCollection obviously creates an item immediately, but not its visual elements (they are however added afterwards, I just don't know exactly when). How can I do this same behaviour without having to pop the message box up?
Actually, I was trying to get this to work and I found the ".UpdateLayout()" function, which works perfectly for me. I realize that you're doing vertical and I'm doing horizontal, but here's my code, it's pretty simple:
for (int i = 0; i < listOfItems.ItemsIn.Count; ++i)
{
//CalculateItemsHeight(i);
ListBoxItem abc = (lb.ItemContainerGenerator.ContainerFromItem(lb.Items[i]) as ListBoxItem);
abc.UpdateLayout();
totalWidth += abc.ActualWidth;
}
Hopefully this helps!
The wpf layout engine won't have been through the layout and arrange pass so your listboxitems won't have been given a size yet. Sticking in the message box will allow the background threads that do this run. Try forcing a call to Measure() on your items before looking at their size.
SOLVED
This creates somewhat flickering effect for a fraction of second (as if loading items one by one), but actually suits my needs.
The point is to refresh the UI for an item before retrieving its height.
I have created an extension method:
public static void RefreshUI(this DependencyObject obj)
{
obj.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Loaded, (Action)delegate { });
}
And then before retrieving the height, I refresh the UI.
private double CalculateItemsHeight(int index)
{
ListBoxItem lbi = _box.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem;
if (lbi != null) {
lbi.RefreshUI();
return lbi.ActualHeight;
}
return 0;
}