Hello,
Is there someway to hide the scrollbar of the XamScheduleView? I have a stackpanel with multiple XamScheduleView's, each scheduleview representing one day, stacked on top of each other vertically. I'd like to have it so that the individual scheduleview's don't have a scrollbar and instead use the scrollbar of the stackpanel to navigate horizontally - this way as I scroll I can view the same time period for all the day's at the same time.
During debugging I've found the TimeslotScrollBarVisibility property, and when set to Hidden it does what I'm looking for, but I can't figure out a way to access this property in code/XAML.
Alternatively, having someway to synchronize the scrollbars together would accomplish the same goal. Is there anyway to synchronize the scrollbars of XamScheduleViews? (Although with this solution I'd still like to hide the individual scrollbars and only have a single scrollbar that controls the scrolling for all the scheduleviews.)
Thank you!
Well, I've figured out to get rid of the scrollbar by creating a custom ControlTemplate, but it doesn't do any good because the control still behaves the way it would if the scrollbar existed - i.e. the control's width doesn't resize when you change the interval.
Is there someway to define the width of the XamScheduleView, or make it so that the control is always 100% of it's width (so there's no scrolling needed from the control's scrollbar)? The width property always seems to be set to "NaN". If I could dynamically resize the control when the interval is changed then I think I'd be able to come up with a solution that will work for me.
A scrollbar is used to expose the scroll range - not define it - if that is what you mean. This is the same in any scrollable control. In the case of the xamSchedule the timeslots are a minimum width based on the preferrred width of a ScheduleViewTimeslotHeader. If there is less room than required to show all the timeslots horizontally using that value then scrolling is enabled whether a scrollbar is present or not. I thought you wanted to have the scrolling of all synchronized but only shown in 1 control. If you want that you should just create some attached properties to help with that. For example, you might create one for the scrollbar offset. You can use a two-way binding between the offset of the scrollbar to the control and then two way bind between that property on the control and another xamschedule. e.g.
<Grid xmlns:tshelper="clr-namespace:ScheduleHelper"> <Grid.Resources> <igPrim:ScheduleResourceString x:Key="BlockingErrorLiteral" ResourceName="BlockingErrorLiteral"/> <igPrim:ValueConverterGroup x:Key="nullToVisibility"> <!-- if null sets visibility to Collapsed --> <igPrim:FixedValueConverter SourceValue="{x:Null}"> <igPrim:FixedValueConverter.DestinationValue> <Visibility>Collapsed</Visibility> </igPrim:FixedValueConverter.DestinationValue> </igPrim:FixedValueConverter> <!-- if not null sets visibility to Visible --> <igPrim:FixedValueConverter> <igPrim:FixedValueConverter.DestinationValue> <Visibility>Visible</Visibility> </igPrim:FixedValueConverter.DestinationValue> </igPrim:FixedValueConverter> </igPrim:ValueConverterGroup> <Style x:Key="BlockingErrorStyle" TargetType="ContentControl"> <!-- AS 12/16/10 TFS61923 --> <Setter Property="igPrim:XamlHelper.Focusable" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ContentControl"> <Grid > <Grid.RowDefinitions> <RowDefinition Height ="Auto"/> <RowDefinition Height ="Auto"/> <RowDefinition Height ="*"/> </Grid.RowDefinitions> <Rectangle Grid.RowSpan="3" Opacity=".9" Fill="{Binding Path=(igPrim:CalendarBrushProvider.BrushProvider)[BlockingErrorBackground], RelativeSource={RelativeSource TemplatedParent}}"/> <!--Header--> <ContentControl Grid.Row="0" FontWeight="Bold" Foreground="{Binding Path=(igPrim:CalendarBrushProvider.BrushProvider)[BlockingErrorHeaderForeground], RelativeSource={RelativeSource TemplatedParent}}" Content="{Binding Source={StaticResource BlockingErrorLiteral}, Path=Value}" Margin="5"> <ContentControl.FontSize> <Binding Path="FontSize" RelativeSource="{RelativeSource TemplatedParent}"> <Binding.Converter> <igPrim:MinMaxConverter MinValue="14"/> </Binding.Converter> </Binding> </ContentControl.FontSize> <!-- JJD 9/19/11 - TFS87912 Use binding instead to the Value property of the ScheduleResourceString so we can get notified when the resources are changed. <igPrim:ScheduleResourceString ResourceName="BlockingErrorLiteral"/>--> </ContentControl> <!--Description--> <TextBlock Grid.Row="1" Margin="5,0,5,0" FontWeight="Bold" TextWrapping="Wrap" Foreground="{Binding Path=(igPrim:CalendarBrushProvider.BrushProvider)[BlockingErrorForeground], RelativeSource={RelativeSource TemplatedParent}}" Text="{Binding Path=Content.UserErrorText, RelativeSource={RelativeSource TemplatedParent}}" Visibility="{Binding Path=Content.UserErrorText, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource nullToVisibility}}" > <TextBlock.FontSize> <Binding Path="FontSize" RelativeSource="{RelativeSource TemplatedParent}"> <Binding.Converter> <igPrim:MinMaxConverter MinValue="12.5"/> </Binding.Converter> </Binding> </TextBlock.FontSize> </TextBlock> <!--Details--> <ScrollViewer Grid.Row="2" Margin="5" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <TextBox Text="{Binding Path=Content, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" Foreground="{Binding Path=(igPrim:CalendarBrushProvider.BrushProvider)[BlockingErrorForeground], RelativeSource={RelativeSource TemplatedParent}}" IsReadOnly="True" TextWrapping="Wrap" Background="{x:Null}"/> </ScrollViewer> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="ig:XamScheduleView"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ig:XamScheduleView"> <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" igPrim:XamlHelper.SnapsToDevicePixels="True"> <VisualStateManager.VisualStateGroups> <VisualStateGroup Name="ErrorStates"> <VisualState Name="NoError"/> <VisualState Name="Error"> <Storyboard Storyboard.TargetName="ErrorDisplay" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid x:Name="RootPanel" > <ig:GridBagPanel igPrim:XamlHelper.SnapsToDevicePixels="{TemplateBinding igPrim:XamlHelper.SnapsToDevicePixels}"> <TextBlock Text="{TemplateBinding SecondaryTimeZoneLabel}" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,6,8,0" ig:GridBagPanel.Row="0" ig:GridBagPanel.Column="0" Visibility="{TemplateBinding SecondaryTimeZoneVisibility}" /> <TextBlock Text="{TemplateBinding PrimaryTimeZoneLabel}" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,6,8,0" ig:GridBagPanel.Row="1" ig:GridBagPanel.Column="0" /> <igPrim:ScheduleViewTimeslotHeaderArea x:Name="SecondaryTimeZone" Background="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaBackgroundScheduleView], RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaSeparator], RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="0,0,0,1" ig:GridBagPanel.Row="0" ig:GridBagPanel.Column="2" Visibility="{TemplateBinding SecondaryTimeZoneVisibility}" ig:GridBagPanel.ColumnWeight="1" /> <igPrim:ScheduleViewTimeslotHeaderArea x:Name="PrimaryTimeZone" Background="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaBackgroundScheduleView], RelativeSource={RelativeSource TemplatedParent}}" ig:GridBagPanel.Row="1" ig:GridBagPanel.Column="2" ig:GridBagPanel.ColumnWeight="1" /> <igPrim:ScheduleStackPanel x:Name="GroupHeadersPanel" ig:GridBagPanel.Row="2" ig:GridBagPanel.Column="0" ig:GridBagPanel.RowWeight="1" Visibility="{TemplateBinding CalendarHeaderAreaVisibilityResolved}" /> <igPrim:ScheduleStackPanel x:Name="GroupsPanel" ig:GridBagPanel.Row="2" ig:GridBagPanel.Column="2" ig:GridBagPanel.ColumnWeight="1" ig:GridBagPanel.RowWeight="1" /> <ScrollBar ig:GridBagPanel.Column="3" ig:GridBagPanel.Row="2" Orientation="Vertical" Style="{TemplateBinding ScrollBarStyle}" x:Name="TimeslotGroupScrollBar" ig:GridBagPanel.RowWeight="1" /> <ScrollBar ig:GridBagPanel.Column="2" ig:GridBagPanel.Row="3" ig:GridBagPanel.ColumnWeight="1" Orientation="Horizontal" Style="{TemplateBinding ScrollBarStyle}" MinHeight="0" ig:GridBagPanel.PreferredHeight="{TemplateBinding tshelper:TimeslotHelper.ScrollBarHeight}" Value="{Binding Path=(tshelper:TimeslotHelper.ScrollBarOffset), RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" x:Name="TimeslotScrollBar" /> <Rectangle igPrim:XamlHelper.SnapsToDevicePixels="True" Fill="{Binding Path=DefaultBrushProvider[TimeslotHeaderTickmarkScheduleView], RelativeSource={RelativeSource TemplatedParent}}" ig:GridBagPanel.Row="0" ig:GridBagPanel.Column="1" ig:GridBagPanel.RowSpan="4" ig:GridBagPanel.ColumnWeight="0" ig:GridBagPanel.PreferredWidth="1" Width="2"/> <!-- AS 11/11/10 NA 11.1 - CalendarHeaderAreaWidth --> <!-- Overlap the rectangle above. --> <igPrim:ScheduleResizerBar x:Name="GroupHeadersResizer" IsEnabled="{TemplateBinding AllowCalendarHeaderAreaResizing}" Canvas.ZIndex="2" Width="4" ig:GridBagPanel.Column="0" ig:GridBagPanel.ColumnSpan="2" ig:GridBagPanel.Row="0" ig:GridBagPanel.RowSpan="4" HorizontalAlignment="Right" /> </ig:GridBagPanel> <ContentControl x:Name="ErrorDisplay" Visibility="Collapsed" Content="{Binding Path=BlockingError, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource BlockingErrorStyle}" igPrim:CalendarBrushProvider.BrushProvider="{Binding Path=DefaultBrushProvider, RelativeSource={RelativeSource TemplatedParent}}" /> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Grid.Resources> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <ig:XamScheduleView DataManager="{Binding ElementName=dm}" x:Name="sv1" Grid.Row="0" tshelper:TimeslotHelper.ScrollBarHeight="0" tshelper:TimeslotHelper.ScrollBarOffset="{Binding ElementName=sv4, Mode=TwoWay, Path=(tshelper:TimeslotHelper.ScrollBarOffset)}" /> <ig:XamScheduleView DataManager="{Binding ElementName=dm}" x:Name="sv2" Grid.Row="1" tshelper:TimeslotHelper.ScrollBarHeight="0" tshelper:TimeslotHelper.ScrollBarOffset="{Binding ElementName=sv4, Mode=TwoWay, Path=(tshelper:TimeslotHelper.ScrollBarOffset)}" /> <ig:XamScheduleView DataManager="{Binding ElementName=dm}" x:Name="sv3" Grid.Row="2" tshelper:TimeslotHelper.ScrollBarHeight="0" tshelper:TimeslotHelper.ScrollBarOffset="{Binding ElementName=sv4, Mode=TwoWay, Path=(tshelper:TimeslotHelper.ScrollBarOffset)}" /> <ig:XamScheduleView DataManager="{Binding ElementName=dm}" x:Name="sv4" Grid.Row="3" /> </Grid>
<igPrim:ValueConverterGroup x:Key="nullToVisibility"> <!-- if null sets visibility to Collapsed --> <igPrim:FixedValueConverter SourceValue="{x:Null}"> <igPrim:FixedValueConverter.DestinationValue> <Visibility>Collapsed</Visibility> </igPrim:FixedValueConverter.DestinationValue> </igPrim:FixedValueConverter>
<!-- if not null sets visibility to Visible --> <igPrim:FixedValueConverter> <igPrim:FixedValueConverter.DestinationValue> <Visibility>Visible</Visibility> </igPrim:FixedValueConverter.DestinationValue> </igPrim:FixedValueConverter>
</igPrim:ValueConverterGroup>
<Style x:Key="BlockingErrorStyle" TargetType="ContentControl"> <!-- AS 12/16/10 TFS61923 --> <Setter Property="igPrim:XamlHelper.Focusable" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ContentControl"> <Grid > <Grid.RowDefinitions> <RowDefinition Height ="Auto"/> <RowDefinition Height ="Auto"/> <RowDefinition Height ="*"/> </Grid.RowDefinitions>
<Rectangle Grid.RowSpan="3" Opacity=".9" Fill="{Binding Path=(igPrim:CalendarBrushProvider.BrushProvider)[BlockingErrorBackground], RelativeSource={RelativeSource TemplatedParent}}"/>
<!--Header--> <ContentControl Grid.Row="0" FontWeight="Bold" Foreground="{Binding Path=(igPrim:CalendarBrushProvider.BrushProvider)[BlockingErrorHeaderForeground], RelativeSource={RelativeSource TemplatedParent}}" Content="{Binding Source={StaticResource BlockingErrorLiteral}, Path=Value}" Margin="5"> <ContentControl.FontSize> <Binding Path="FontSize" RelativeSource="{RelativeSource TemplatedParent}"> <Binding.Converter> <igPrim:MinMaxConverter MinValue="14"/> </Binding.Converter> </Binding> </ContentControl.FontSize> <!-- JJD 9/19/11 - TFS87912 Use binding instead to the Value property of the ScheduleResourceString so we can get notified when the resources are changed. <igPrim:ScheduleResourceString ResourceName="BlockingErrorLiteral"/>--> </ContentControl>
<!--Description--> <TextBlock Grid.Row="1" Margin="5,0,5,0" FontWeight="Bold" TextWrapping="Wrap" Foreground="{Binding Path=(igPrim:CalendarBrushProvider.BrushProvider)[BlockingErrorForeground], RelativeSource={RelativeSource TemplatedParent}}" Text="{Binding Path=Content.UserErrorText, RelativeSource={RelativeSource TemplatedParent}}" Visibility="{Binding Path=Content.UserErrorText, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource nullToVisibility}}" > <TextBlock.FontSize> <Binding Path="FontSize" RelativeSource="{RelativeSource TemplatedParent}"> <Binding.Converter> <igPrim:MinMaxConverter MinValue="12.5"/> </Binding.Converter> </Binding> </TextBlock.FontSize>
</TextBlock>
<!--Details--> <ScrollViewer Grid.Row="2" Margin="5" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding Path=Content, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" Foreground="{Binding Path=(igPrim:CalendarBrushProvider.BrushProvider)[BlockingErrorForeground], RelativeSource={RelativeSource TemplatedParent}}" IsReadOnly="True" TextWrapping="Wrap" Background="{x:Null}"/>
</ScrollViewer>
</Grid>
</ControlTemplate> </Setter.Value> </Setter> </Style>
<Style TargetType="ig:XamScheduleView"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ig:XamScheduleView"> <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" igPrim:XamlHelper.SnapsToDevicePixels="True">
<VisualStateManager.VisualStateGroups> <VisualStateGroup Name="ErrorStates"> <VisualState Name="NoError"/> <VisualState Name="Error"> <Storyboard Storyboard.TargetName="ErrorDisplay" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups>
<Grid x:Name="RootPanel" > <ig:GridBagPanel igPrim:XamlHelper.SnapsToDevicePixels="{TemplateBinding igPrim:XamlHelper.SnapsToDevicePixels}"> <TextBlock Text="{TemplateBinding SecondaryTimeZoneLabel}" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,6,8,0" ig:GridBagPanel.Row="0" ig:GridBagPanel.Column="0" Visibility="{TemplateBinding SecondaryTimeZoneVisibility}" /> <TextBlock Text="{TemplateBinding PrimaryTimeZoneLabel}" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,6,8,0" ig:GridBagPanel.Row="1" ig:GridBagPanel.Column="0" />
<igPrim:ScheduleViewTimeslotHeaderArea x:Name="SecondaryTimeZone" Background="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaBackgroundScheduleView], RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaSeparator], RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="0,0,0,1" ig:GridBagPanel.Row="0" ig:GridBagPanel.Column="2" Visibility="{TemplateBinding SecondaryTimeZoneVisibility}" ig:GridBagPanel.ColumnWeight="1" /> <igPrim:ScheduleViewTimeslotHeaderArea x:Name="PrimaryTimeZone" Background="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaBackgroundScheduleView], RelativeSource={RelativeSource TemplatedParent}}" ig:GridBagPanel.Row="1" ig:GridBagPanel.Column="2" ig:GridBagPanel.ColumnWeight="1" />
<igPrim:ScheduleStackPanel x:Name="GroupHeadersPanel" ig:GridBagPanel.Row="2" ig:GridBagPanel.Column="0" ig:GridBagPanel.RowWeight="1" Visibility="{TemplateBinding CalendarHeaderAreaVisibilityResolved}" />
<igPrim:ScheduleStackPanel x:Name="GroupsPanel" ig:GridBagPanel.Row="2" ig:GridBagPanel.Column="2" ig:GridBagPanel.ColumnWeight="1" ig:GridBagPanel.RowWeight="1" />
<ScrollBar ig:GridBagPanel.Column="3" ig:GridBagPanel.Row="2" Orientation="Vertical" Style="{TemplateBinding ScrollBarStyle}" x:Name="TimeslotGroupScrollBar" ig:GridBagPanel.RowWeight="1" /> <ScrollBar ig:GridBagPanel.Column="2" ig:GridBagPanel.Row="3" ig:GridBagPanel.ColumnWeight="1" Orientation="Horizontal" Style="{TemplateBinding ScrollBarStyle}" MinHeight="0" ig:GridBagPanel.PreferredHeight="{TemplateBinding tshelper:TimeslotHelper.ScrollBarHeight}" Value="{Binding Path=(tshelper:TimeslotHelper.ScrollBarOffset), RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" x:Name="TimeslotScrollBar" /> <Rectangle igPrim:XamlHelper.SnapsToDevicePixels="True" Fill="{Binding Path=DefaultBrushProvider[TimeslotHeaderTickmarkScheduleView], RelativeSource={RelativeSource TemplatedParent}}" ig:GridBagPanel.Row="0" ig:GridBagPanel.Column="1" ig:GridBagPanel.RowSpan="4" ig:GridBagPanel.ColumnWeight="0" ig:GridBagPanel.PreferredWidth="1" Width="2"/>
<!-- AS 11/11/10 NA 11.1 - CalendarHeaderAreaWidth --> <!-- Overlap the rectangle above. --> <igPrim:ScheduleResizerBar x:Name="GroupHeadersResizer" IsEnabled="{TemplateBinding AllowCalendarHeaderAreaResizing}" Canvas.ZIndex="2" Width="4" ig:GridBagPanel.Column="0" ig:GridBagPanel.ColumnSpan="2" ig:GridBagPanel.Row="0" ig:GridBagPanel.RowSpan="4" HorizontalAlignment="Right" /> </ig:GridBagPanel>
<ContentControl x:Name="ErrorDisplay" Visibility="Collapsed" Content="{Binding Path=BlockingError, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource BlockingErrorStyle}" igPrim:CalendarBrushProvider.BrushProvider="{Binding Path=DefaultBrushProvider, RelativeSource={RelativeSource TemplatedParent}}" />
</Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Grid.Resources> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <ig:XamScheduleView DataManager="{Binding ElementName=dm}" x:Name="sv1" Grid.Row="0" tshelper:TimeslotHelper.ScrollBarHeight="0" tshelper:TimeslotHelper.ScrollBarOffset="{Binding ElementName=sv4, Mode=TwoWay, Path=(tshelper:TimeslotHelper.ScrollBarOffset)}" /> <ig:XamScheduleView DataManager="{Binding ElementName=dm}" x:Name="sv2" Grid.Row="1" tshelper:TimeslotHelper.ScrollBarHeight="0" tshelper:TimeslotHelper.ScrollBarOffset="{Binding ElementName=sv4, Mode=TwoWay, Path=(tshelper:TimeslotHelper.ScrollBarOffset)}" /> <ig:XamScheduleView DataManager="{Binding ElementName=dm}" x:Name="sv3" Grid.Row="2" tshelper:TimeslotHelper.ScrollBarHeight="0" tshelper:TimeslotHelper.ScrollBarOffset="{Binding ElementName=sv4, Mode=TwoWay, Path=(tshelper:TimeslotHelper.ScrollBarOffset)}" /> <ig:XamScheduleView DataManager="{Binding ElementName=dm}" x:Name="sv4" Grid.Row="3" /> </Grid>
The TimeslotHelper referenced here is just a simple class with some attached properties. e.g.
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows; namespace ScheduleHelper{ public static class TimeslotHelper { #region ScrollBarHeight public static readonly DependencyProperty ScrollBarHeightProperty = DependencyProperty.RegisterAttached("ScrollBarHeight", typeof(double), typeof(TimeslotHelper), new PropertyMetadata(double.NaN) ); public static double GetScrollBarHeight(DependencyObject d) { return (double)d.GetValue(TimeslotHelper.ScrollBarHeightProperty); } public static void SetScrollBarHeight(DependencyObject d, double value) { d.SetValue(TimeslotHelper.ScrollBarHeightProperty, value); } #endregion //ScrollBarHeight #region ScrollBarOffset public static readonly DependencyProperty ScrollBarOffsetProperty = DependencyProperty.RegisterAttached("ScrollBarOffset", typeof(double), typeof(TimeslotHelper), new PropertyMetadata(0d) ); public static double GetScrollBarOffset(DependencyObject d) { return (double)d.GetValue(TimeslotHelper.ScrollBarOffsetProperty); } public static void SetScrollBarOffset(DependencyObject d, double value) { d.SetValue(TimeslotHelper.ScrollBarOffsetProperty, value); } #endregion //ScrollBarOffset }}
namespace ScheduleHelper{ public static class TimeslotHelper { #region ScrollBarHeight
public static readonly DependencyProperty ScrollBarHeightProperty = DependencyProperty.RegisterAttached("ScrollBarHeight", typeof(double), typeof(TimeslotHelper), new PropertyMetadata(double.NaN) );
public static double GetScrollBarHeight(DependencyObject d) { return (double)d.GetValue(TimeslotHelper.ScrollBarHeightProperty); }
public static void SetScrollBarHeight(DependencyObject d, double value) { d.SetValue(TimeslotHelper.ScrollBarHeightProperty, value); }
#endregion //ScrollBarHeight
#region ScrollBarOffset
public static readonly DependencyProperty ScrollBarOffsetProperty = DependencyProperty.RegisterAttached("ScrollBarOffset", typeof(double), typeof(TimeslotHelper), new PropertyMetadata(0d) );
public static double GetScrollBarOffset(DependencyObject d) { return (double)d.GetValue(TimeslotHelper.ScrollBarOffsetProperty); }
public static void SetScrollBarOffset(DependencyObject d, double value) { d.SetValue(TimeslotHelper.ScrollBarOffsetProperty, value); }
#endregion //ScrollBarOffset }}
What is happening is that you set the attached ScrollBarHeight on each xamSchedule (except the last) to 0. The template has the preferredheight of the scrollbar bound to that property (and the Minimum of the scrollbar set to 0 since the default style has it set to 17 or maybe its a system value). That allows you to hide the scrollbar from each but the last control. Then the Value property of the ScrollBar is two way bound to the control using the other attached property - ScrollBarOffset. Then each instance of the xamScheduleView has that property two way bound to the last xamSchedule so the scrolling is synchronized.
Note if you want to see the xamSchedule showing each day on a separate row I would recommend submitting a suggestion for adding this though since it would probably be best if the control did it since it could support any # of days whereas you need to have a control for each and manage the VisibleDates of each. Also, it would allow selection across the days whereas in this case each control has its own selection, edit state, active group, etc. If you do submit a suggestion please be sure to include as much detail as possible as there are many interpretations of this type of behavior - e.g. I would expect that if we did this in the control that the day header identifying which day's timeslots are displayed would be to the left side of the timeslots similar to how the day header in day view is fixed at the top of the vertically scrolling timeslots.
Ok so you have the controls within an element that is measuring its children with infinity? E.g. a horizontal stackpanel (stackpanels only measure with infinity in the direction they are arranging and width the value of the availablesize for the other constraint), a grid where the associated column definition has a width of auto, a scrollviewer where the horizontalscrollbarvisibility is auto, hidden or visible. Note, just having it within a window whose SizeToContent is Width is not enough because in that case the Window measures its children with the virtual screen width - not infinity. In that situation then the desired size should be based upon the default/preferred timeslotheader width * the number of timeslots so the control width should change.
I tried this out and currently it won't when using our GridBagPanel in the control template but if you change it to Grid and change the attached properties used in the template then it does. It is important to note though that if the width of the ancestor is less than the desired width then part of the control will be off screen. If you put it in a scrollviewer as you seem to indicate you are doing then you will be able to scroll the contents but realize that (a) there will no virtualization of elements since virtualization is based on being measured with the actual on screen available size and (b) if the scrollviewer will contain the xamschedule views then it will be scrolling the entire control and therefore the tabs on the left will scroll out of view.
Here's a modified version of the style I provided earlier but this one uses a Grid instead of a GridBagPanel.
<Style TargetType="ig:XamScheduleView"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ig:XamScheduleView"> <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" igPrim:XamlHelper.SnapsToDevicePixels="True"> <VisualStateManager.VisualStateGroups> <VisualStateGroup Name="ErrorStates"> <VisualState Name="NoError"/> <VisualState Name="Error"> <Storyboard Storyboard.TargetName="ErrorDisplay" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid x:Name="RootPanel" > <Grid igPrim:XamlHelper.SnapsToDevicePixels="{TemplateBinding igPrim:XamlHelper.SnapsToDevicePixels}"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Text="{TemplateBinding SecondaryTimeZoneLabel}" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,6,8,0" Grid.Row="0" Grid.Column="0" Visibility="{TemplateBinding SecondaryTimeZoneVisibility}" /> <TextBlock Text="{TemplateBinding PrimaryTimeZoneLabel}" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,6,8,0" Grid.Row="1" Grid.Column="0" /> <igPrim:ScheduleViewTimeslotHeaderArea x:Name="SecondaryTimeZone" Background="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaBackgroundScheduleView], RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaSeparator], RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="0,0,0,1" Grid.Row="0" Grid.Column="2" Visibility="{TemplateBinding SecondaryTimeZoneVisibility}" /> <igPrim:ScheduleViewTimeslotHeaderArea x:Name="PrimaryTimeZone" Background="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaBackgroundScheduleView], RelativeSource={RelativeSource TemplatedParent}}" Grid.Row="1" Grid.Column="2" /> <igPrim:ScheduleStackPanel x:Name="GroupHeadersPanel" Grid.Row="2" Grid.Column="0" Visibility="{TemplateBinding CalendarHeaderAreaVisibilityResolved}" /> <igPrim:ScheduleStackPanel x:Name="GroupsPanel" Grid.Row="2" Grid.Column="2" /> <ScrollBar Grid.Column="3" Grid.Row="2" Orientation="Vertical" Style="{TemplateBinding ScrollBarStyle}" x:Name="TimeslotGroupScrollBar" /> <ScrollBar Grid.Column="2" Grid.Row="3" Orientation="Horizontal" Style="{TemplateBinding ScrollBarStyle}" MinHeight="0" Height="{TemplateBinding tshelper:TimeslotHelper.ScrollBarHeight}" Value="{Binding Path=(tshelper:TimeslotHelper.ScrollBarOffset), RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" x:Name="TimeslotScrollBar" /> <Rectangle igPrim:XamlHelper.SnapsToDevicePixels="True" Fill="{Binding Path=DefaultBrushProvider[TimeslotHeaderTickmarkScheduleView], RelativeSource={RelativeSource TemplatedParent}}" Grid.Row="0" Grid.Column="1" Grid.RowSpan="4" Width="2"/> <!-- AS 11/11/10 NA 11.1 - CalendarHeaderAreaWidth --> <!-- Overlap the rectangle above. --> <igPrim:ScheduleResizerBar x:Name="GroupHeadersResizer" IsEnabled="{TemplateBinding AllowCalendarHeaderAreaResizing}" Canvas.ZIndex="2" Width="4" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Grid.RowSpan="4" HorizontalAlignment="Right" /> </Grid> <ContentControl x:Name="ErrorDisplay" Visibility="Collapsed" Content="{Binding Path=BlockingError, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource BlockingErrorStyle}" igPrim:CalendarBrushProvider.BrushProvider="{Binding Path=DefaultBrushProvider, RelativeSource={RelativeSource TemplatedParent}}" /> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
<Grid x:Name="RootPanel" > <Grid igPrim:XamlHelper.SnapsToDevicePixels="{TemplateBinding igPrim:XamlHelper.SnapsToDevicePixels}"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Text="{TemplateBinding SecondaryTimeZoneLabel}" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,6,8,0" Grid.Row="0" Grid.Column="0" Visibility="{TemplateBinding SecondaryTimeZoneVisibility}" /> <TextBlock Text="{TemplateBinding PrimaryTimeZoneLabel}" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,6,8,0" Grid.Row="1" Grid.Column="0" />
<igPrim:ScheduleViewTimeslotHeaderArea x:Name="SecondaryTimeZone" Background="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaBackgroundScheduleView], RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaSeparator], RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="0,0,0,1" Grid.Row="0" Grid.Column="2" Visibility="{TemplateBinding SecondaryTimeZoneVisibility}" /> <igPrim:ScheduleViewTimeslotHeaderArea x:Name="PrimaryTimeZone" Background="{Binding Path=DefaultBrushProvider[TimeslotHeaderAreaBackgroundScheduleView], RelativeSource={RelativeSource TemplatedParent}}" Grid.Row="1" Grid.Column="2" />
<igPrim:ScheduleStackPanel x:Name="GroupHeadersPanel" Grid.Row="2" Grid.Column="0" Visibility="{TemplateBinding CalendarHeaderAreaVisibilityResolved}" />
<igPrim:ScheduleStackPanel x:Name="GroupsPanel" Grid.Row="2" Grid.Column="2" />
<ScrollBar Grid.Column="3" Grid.Row="2" Orientation="Vertical" Style="{TemplateBinding ScrollBarStyle}" x:Name="TimeslotGroupScrollBar" /> <ScrollBar Grid.Column="2" Grid.Row="3" Orientation="Horizontal" Style="{TemplateBinding ScrollBarStyle}" MinHeight="0" Height="{TemplateBinding tshelper:TimeslotHelper.ScrollBarHeight}" Value="{Binding Path=(tshelper:TimeslotHelper.ScrollBarOffset), RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" x:Name="TimeslotScrollBar" /> <Rectangle igPrim:XamlHelper.SnapsToDevicePixels="True" Fill="{Binding Path=DefaultBrushProvider[TimeslotHeaderTickmarkScheduleView], RelativeSource={RelativeSource TemplatedParent}}" Grid.Row="0" Grid.Column="1" Grid.RowSpan="4" Width="2"/>
<!-- AS 11/11/10 NA 11.1 - CalendarHeaderAreaWidth --> <!-- Overlap the rectangle above. --> <igPrim:ScheduleResizerBar x:Name="GroupHeadersResizer" IsEnabled="{TemplateBinding AllowCalendarHeaderAreaResizing}" Canvas.ZIndex="2" Width="4" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Grid.RowSpan="4" HorizontalAlignment="Right" /> </Grid>
</Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
Thank you, that solution was exactly what I was hoping for!
Now that the scrolling issue is solved I've noticed another small issue. It has to do with the way short side-by-side appointments are displayed when the timeslot interval gets bigger. For example, If I create a number of 1 or 2 minute long appointments beside each other and then change the timeslot interval, they start stacking on top of each other. For example:
A number of short appointments created beside each other with the interval set to 1 minute.
When the interval is changed to 15 minutes the appointments start stacking on top of each other...
Then at 60 minutes it starts stacking on multiple levels so that a little arrow appears at the bottom of the ScheduleView to show the rest of the levels (3 in all, shown in the above 3 screen shots).
Is there some way to do something about this so that the side-by-side appointments don't stack up on top of each other when the interval increases? Or if it's not possible, is there someway to at least prevent it from stacking on multiple levels so that those little arrows are necessary to view the rest of the levels? I think we could possibly handle the way it looks at a 15 minute interval, but I have a feeling that the way it looks at 60 minutes with the little arrows will not work for the users.
On a related note, how is the style of an appointment set (or more specifically, the height)? I inherited maintenance of this screen and the person who initially set up this control is not here anymore. I've tried to find where the style is set but haven't been successful so far...
JPUser said:Is there some way to do something about this so that the side-by-side appointments don't stack up on top of each other when the interval increases?
No. The activities are positioned at the pixel associated with their start time. Just as with Outlook if activities in a schedule view overlap then they are shifted to another row.
JPUser said:Or if it's not possible, is there someway to at least prevent it from stacking on multiple levels so that those little arrows are necessary to view the rest of the levels?
The out of view indicators are their when there is not enough room to show all the activities. The height of the control does not currently consider the height of the activities since the control is designed to virtualize the timeslots and activities and because each CalendarGroup is intended to be the same size. We can try to see if this is something we can support but I cannot say for sure that we can.
JPUser said:On a related note, how is the style of an appointment set (or more specifically, the height)? I inherited maintenance of this screen and the person who initially set up this control is not here anymore. I've tried to find where the style is set but haven't been successful so far..
An Appointment is represented by an AppointmentPresenter. The style and template for the AppointmentPresenter ultimately dictate the size. If you want to retemplate it then you would look in the DefaultStyles directory for the xamSchedule's xaml and create your own style/template based on the ones there. The activities are relatively complicated because they are responsible for lots of things so their template is somewhat complicated.
Ok, but as you can see in the screenshots, the times of the schedules are not actually overlapping, they are consecutive...
If you want to retemplate it then you would look in the DefaultStyles directory for the xamSchedule's xaml and create your own style/template based on the ones there.
Thank you, I'll give it a try.
Finally, one more question for today - how can I set the interval at which the header labels appear? For example, in the screenshot above the header text is appearing every hour. I'd like to change this value when the timeslot interval is changed (showing the time only at the hour level is not ideal when the timeslot interval is 1 minute...). I tried to find where this value is set, but I can't seem to find it anywhere.
JPUser said: Ok, but as you can see in the screenshots, the times of the schedules are not actually overlapping, they are consecutive...
It doesn't matter whether the times overlap. It matters which pixels the times map to. So if the interval is say 60 minutes and the timeslot is only 30 pixels wide then at most every pixel represents 2 minutes of times. In reality it is more than that because the minimum extent for an activity is like 3 pixels.
JPUser said:Finally, one more question for today - how can I set the interval at which the header labels appear? For example, in the screenshot above the header text is appearing every hour. I'd like to change this value when the timeslot interval is changed (showing the time only at the hour level is not ideal when the timeslot interval is 1 minute...). I tried to find where this value is set, but I can't seem to find it anywhere.
Thank you Marianne. I'll try to fill in more details where I can when I get a bit of free time.
Hi,
Andrew asked me to let you know that he has created a development issue concerning using the xamScheduleView in an infinite width container and wanting the width to adjust when the timeslot interval is changed.
I have created a private case for you, CAS-86118-LHGM6J, and will contact you directly thru your case.
I will also be creating separate cases for you related to your other questions and you will be hearing from me shortly thru your cases.
JPUser said:I don't suppose there is a way to set the major interval (like in the datachart for example)?
No but you can submit a suggestoin for adding that. For now you would need to create your own template to provide the functionality you want.
JPUser said: Is there a way to change the minimum extent for an activity?
Is there a way to change the minimum extent for an activity?
No, the minimum of 3 pixels is just so it doesn't appear like a line. Again the size really is based upon how the time maps to the size of the timeslots. You could provide a MinWidth on the ScheduleViewTimeslotHeader and that would affect the preferred size of the timeslot headers so you could make the timeslots wider.
The headers were designed to emulate Office so the default will only show the headers on the major interval.
I don't suppose there is a way to set the major interval (like in the datachart for example)?
In reality it is more than that because the minimum extent for an activity is like 3 pixels.