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.
Thanks for your reply Andrew. I previously got the scrollbar synchronizing to work, but found that the solution is still not complete. My main problem is that the width of the ScheduleView control seems to be determined when the control is first loaded, and then after that it doesn't matter if the TimeslotInterval is changed, it always stays the same width. I'm able to set the width of the ScheduleViewTimeslotHeader, which works properly when the ScheduleView control is first loaded, but then after that if I increase the TimeslotInterval (I have a dropdown box where the user can choose what timeslot interval they'd like to view the control at) the width of ScheduleViewTimeslotHeader automatically changes and the total width of the ScheduleView stays the same. For example, if I change the interval from 1 hours to 2 hours, the ScheduleViewTimeslotHeader width automatically doubles and the total width of the ScheduleView control stays the same. If I decrease the TimeslotInterval instead, for example if I change the interval from 1 hour to 30 minutes, the ScheduleViewTimeslotHeader width stays the same, but the total control width also stays the same so that the scrollbar appears.
What I'd like to do is have the total width of the ScheduleView control to recalculate when the TimeslotInterval changes - when the TimeslotInterval increases, I'd like the ScheduleViewTimeslotHeader width to stay the same and the total width of the ScheduleView to decrease. When the TimeslotInterval decreases, I'd like the ScheduleViewTimeslotHeader width to stay the same (as it currently does) but have the total width of the ScheduleView increase.
P.S. is there someway to view your site so that when the window expands the text area also expands? Right now it is quite difficult to view the code you posted as it appears in a narrow, fixed width scroll viewer and there is a huge amount of screen real-estate being taken up by black margins, and if I make my window wider all it does is increase the size of the black margins.
JPUser said:Thanks for your reply Andrew. I previously got the scrollbar synchronizing to work, but found that the solution is still not complete. My main problem is that the width of the ScheduleView control seems to be determined when the control is first loaded, and then after that it doesn't matter if the TimeslotInterval is changed, it always stays the same width.
Maybe you can provide a sample that demonstrates the problem. As I mentioned the width of the timeslot is based on the preferred width of a single ScheduleViewTimeslotHeader. The timeslots will be that width unless there is more room than is needed to show all the timeslots - i.e. if the available space for the timeslotpanel is > # timeslots * preferred width.
JPUser said:I'm able to set the width of the ScheduleViewTimeslotHeader, which works properly when the ScheduleView control is first loaded, but then after that if I increase the TimeslotInterval (I have a dropdown box where the user can choose what timeslot interval they'd like to view the control at) the width of ScheduleViewTimeslotHeader automatically changes and the total width of the ScheduleView stays the same. For example, if I change the interval from 1 hours to 2 hours, the ScheduleViewTimeslotHeader width automatically doubles and the total width of the ScheduleView control stays the same. If I decrease the TimeslotInterval instead, for example if I change the interval from 1 hour to 30 minutes, the ScheduleViewTimeslotHeader width stays the same, but the total control width also stays the same so that the scrollbar appears.
I'm not following. If you set the Width then the element will be that size which really isn't a good idea. Rather than trying to guess as to how you have this set up it would be better if you provide a functional sample that demonstrates the issue so I can run it and see the behaviors you are seeing. Then I can make recommendations or explain what is happening.
JPUser said: What I'd like to do is have the total width of the ScheduleView control to recalculate when the TimeslotInterval changes - when the TimeslotInterval increases, I'd like the ScheduleViewTimeslotHeader width to stay the same and the total width of the ScheduleView to decrease. When the TimeslotInterval decreases, I'd like the ScheduleViewTimeslotHeader width to stay the same (as it currently does) but have the total width of the ScheduleView increase.
I'm not sure what you mean by having the width of the control recalculate. Are you putting this into some element that measures the element with infinity (like a horizontal stack panel or grid whose column width is auto?). If so you should be aware that you're disabling any possibility of virtualization. If you are going this route then it's possible you'll need to change some panels being used - like using a Grid with * columns instead of a GridBagPanel as we use in the default template. Maybe your sample will make this clearer.
JPUser said: P.S. is there someway to view your site so that when the window expands the text area also expands? Right now it is quite difficult to view the code you posted as it appears in a narrow, fixed width scroll viewer and there is a huge amount of screen real-estate being taken up by black margins, and if I make my window wider all it does is increase the size of the black margins.
Not that I know of. The forums use a fixed width layout.
Considering how long it would take for me to make a working sample of this program, I'd like to leave that as a final resort. Instead I've added some screen captures to help explain what I'm talking about.
This first screen capture shows the setup with the TimeslotInterval set to 30 minutes. Basically I have a vertical stack panel containing a dynamic number of ScheduleView control's, each control representing a single day running from 5:00 - 5:00 (the next day). The date range is selected at the top left (max 7 days), and the TimeslotInterval is selected from the dropdown at the top right. The interval was set to 30 minutes when the ScheduleView controls were loaded, so the width of the ScheduleView controls is the exact width needed to display the control with the ScheduleViewTimeslotHeader width set to the value specified in the ScheduleView's style.
The second screen capture shows what happens when the TimeslotInterval is changed to 60 minutes - the ScheduleViewTimeslotHeader width automatically doubles and the total width of the ScheduleView controls stays exactly the same. Even though the timeslot interval is twice as big as in the previous screen capture, from the user's point of view the appearance of the control is basically the same between the two, just with less lines in the latter screen...
What I would like to happen would be that the ScheduleViewTimeslotHeader width stays the same as in the first screen capture and instead the total width of the ScheduleView controls reduces by half (like in the last screen capture).
The timeslots will be that width unless there is more room than is needed to show all the timeslots - i.e. if the available space for the timeslotpanel is > # timeslots * preferred width.
So when this happens is there a way to make it so the available space for the timeslotpanel is set to (# timeslots * preferred width) so that it doesn't stretch everything?
The third screen capture shows what happens when the TimeslotInterval is changed to 15 minutes - the ScheduleViewTimeslotHeader width stays the same, the total width of the ScheduleView controls also stays exactly the same, and a scrollbar appears to allow scrolling to the right to view the rest of the day's schedule.
What I'd like to happen here would be, instead of the total width of the ScheduleView control staying the same and a scrollbar appearing, I'd like to have the total width of the ScheduleView control double and then I could scroll all the days at once using the scrollbar of the stackpanel in which the ScheduleView controls are all inside of.
I'm not sure what you mean by having the width of the control recalculate.
I mean I'd like the width demonstrated in the screen capture above to change so that it's always 100% the width of the ScheduleView, without needed to scroll the ScheduleView, and without the width of the ScheduleViewTimeslotHeader changing to make it expand to a certain width. As you can see in all the screen captures the width of the ScheduleView controls is exactly the same no matter what the interval (and also no matter the size of the window). This width seems to be calculated once when the ScheduleView control is loaded, and then it never changes after that. If I load the control again with a different interval set, then the total width of the ScheduleView control changes (shown in the screen capture below where the ScheduleView controls were loaded with the TimeslotInterval set to 60 minutes). I'd like to have this width change when the TimeslotInterval is changed.
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...
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.
JPUser said: Ok, but as you can see in the screenshots, the times of the schedules are not actually overlapping, they are consecutive...
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.