I've using Microsoft InteractiveDataDisplay.WPF (former DynamicDataDisplay) to visualize real time data (about 2-3 seconds).
This code xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
double[] y = new double[200];
double[] x = new double[200];
for (int i = 0; i < 200; i++)
{
y[i] = 3.1415 * i / (y.Length - 1);
x[i] = DateTime.Now.AddMinutes(-i).ToOADate();
}
linegraph.Plot(x, y);
}
}
with this xaml:
<d3:Chart Name="plotter">
<d3:Chart.Title>
<TextBlock HorizontalAlignment="Center" FontSize="18" Margin="0,5,0,5">chart sample</TextBlock>
</d3:Chart.Title>
<d3:LineGraph x:Name="linegraph" Description="Simple linegraph" Stroke="Blue" StrokeThickness="3">
</d3:LineGraph>
</d3:Chart>
Give this view:
But I want following custom graph:
Any ideas how to did it? Thanks!
Update 1 (using Kevin Ross solution):
Update 2 (using Dmitry Voytsekhovskiy solution):
But the time axis (Y) not synchronize and not move with data. how to fix this?
How to move axis to the top?
The chart layout template is defined in Themes/Generic.xaml as a style for d3:Chart.
You can create a custom style where the horizontal axis is located at the top (d3:Figure.Placement="Top") and has correct orientation (AxisOrientation="Top"). For instance,
<d3:PlotAxis x:Name="PART_horizontalAxis"
d3:Figure.Placement="Top"
AxisOrientation="Top"
Foreground="{TemplateBinding Foreground}">
<d3:MouseNavigation IsVerticalNavigationEnabled="False"/>
</d3:PlotAxis>
How to use custom formatting for axis labels?
For example, if values along y are actually hours since certain time moment, and you want to show axis ticks as HH:mm, you need to inject a custom label provider into an axis control.
To do that you can create new axis class derived from Axis and pass custom label provider to the base constructor:
public class CustomLabelProvider : ILabelProvider
{
public static DateTime Origin = new DateTime(2000, 1, 1);
public FrameworkElement[] GetLabels(double[] ticks)
{
if (ticks == null)
throw new ArgumentNullException("ticks");
List<TextBlock> Labels = new List<TextBlock>();
foreach (double tick in ticks)
{
TextBlock text = new TextBlock();
var time = Origin + TimeSpan.FromHours(tick);
text.Text = time.ToShortTimeString();
Labels.Add(text);
}
return Labels.ToArray();
}
}
public class CustomAxis : Axis
{
public CustomAxis() : base(new CustomLabelProvider(), new TicksProvider())
{
}
}
Now return to the custom Chart template and change for the vertical axis its type from PlotAxis to CustomAxis (note that you might need to change type prefix):
<d3:CustomAxis x:Name="PART_verticalAxis"
d3:Figure.Placement="Left"
AxisOrientation="Left"
Foreground="{TemplateBinding Foreground}">
<d3:MouseNavigation IsHorizontalNavigationEnabled="False"/>
</d3:CustomAxis>
If we do the described steps for the LineGraphSample and run it, we get the following:
Finally, the custom chart style:
<Style TargetType="d3:Chart">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="d3:Chart">
<Grid>
<d3:Figure x:Name="PART_figure" Margin="1"
PlotHeight="{Binding PlotHeight, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
PlotWidth="{Binding PlotWidth, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
PlotOriginX="{Binding PlotOriginX, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
PlotOriginY="{Binding PlotOriginY, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
IsAutoFitEnabled="{Binding IsAutoFitEnabled, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
AspectRatio="{Binding AspectRatio, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
ExtraPadding="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<d3:MouseNavigation IsVerticalNavigationEnabled="{TemplateBinding IsVerticalNavigationEnabled}"
IsHorizontalNavigationEnabled="{TemplateBinding IsHorizontalNavigationEnabled}"
x:Name="PART_mouseNavigation"/>
<d3:KeyboardNavigation IsVerticalNavigationEnabled="{TemplateBinding IsVerticalNavigationEnabled}"
IsHorizontalNavigationEnabled="{TemplateBinding IsHorizontalNavigationEnabled}"
x:Name="PART_keyboardNavigation"/>
<d3:VerticalContentControl d3:Figure.Placement="Left"
Content="{TemplateBinding LeftTitle}"
VerticalAlignment="Center"
IsTabStop="False"/>
<d3:CustomAxis x:Name="PART_verticalAxis"
d3:Figure.Placement="Left"
AxisOrientation="Left"
Foreground="{TemplateBinding Foreground}">
<d3:MouseNavigation IsHorizontalNavigationEnabled="False"/>
</d3:CustomAxis>
<d3:AxisGrid x:Name="PART_axisGrid"
VerticalTicks="{Binding Ticks,ElementName=PART_verticalAxis, Mode=OneWay}"
HorizontalTicks="{Binding Ticks,ElementName=PART_horizontalAxis, Mode=OneWay}"
Stroke="{TemplateBinding Foreground}" Opacity="0.25"/>
<ContentControl d3:Figure.Placement="Top"
HorizontalAlignment="Center"
FontSize="16"
Content="{TemplateBinding Title}"
Foreground="{TemplateBinding Foreground}"
IsTabStop="False"/>
<ContentControl d3:Figure.Placement="Bottom"
HorizontalAlignment="Center"
Content="{TemplateBinding BottomTitle}"
Foreground="{TemplateBinding Foreground}"
IsTabStop="False"/>
<d3:VerticalContentControl d3:Figure.Placement="Right"
Content="{TemplateBinding RightTitle}"
VerticalAlignment="Center"
IsTabStop="False"/>
<d3:PlotAxis x:Name="PART_horizontalAxis"
d3:Figure.Placement="Top"
AxisOrientation="Top"
Foreground="{TemplateBinding Foreground}">
<d3:MouseNavigation IsVerticalNavigationEnabled="False"/>
</d3:PlotAxis>
<ContentPresenter/>
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding Foreground}" d3:Figure.Placement="Center"/>
<d3:Legend x:Name="PART_legend"
Foreground="Black" Content="{TemplateBinding LegendContent}"
Visibility="{TemplateBinding LegendVisibility}"/>
</d3:Figure>
<Rectangle x:Name="FocusVisualElement" RadiusX="2" RadiusY="2" Stroke="#FF6DBDD1" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsTabStop" Value="False"/>
</Style>
I managed to improve the solution suggested by Dmitry so that the axis remains linked to the graph.
<Style x:Key="timeAxisStyle" TargetType="d3:PlotAxis">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="d3:PlotAxis">
<Grid>
<local:CustomAxis x:Name="PART_Axis"
AxisOrientation="{Binding AxisOrientation, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
IsReversed="{Binding IsReversed, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Ticks="{Binding Ticks, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{Binding Foreground, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>
<ContentPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsTabStop" Value="False"/>
</Style>
<Style TargetType="d3:Chart">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="d3:Chart">
<Grid>
<d3:Figure x:Name="PART_figure" Margin="1"
PlotHeight="{Binding PlotHeight, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
PlotWidth="{Binding PlotWidth, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
PlotOriginX="{Binding PlotOriginX, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
PlotOriginY="{Binding PlotOriginY, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
IsXAxisReversed = "{Binding IsXAxisReversed, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
IsYAxisReversed = "{Binding IsXAxisReversed, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
IsAutoFitEnabled="{Binding IsAutoFitEnabled, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
AspectRatio="{Binding AspectRatio, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
ExtraPadding="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<d3:MouseNavigation IsVerticalNavigationEnabled="{TemplateBinding IsVerticalNavigationEnabled}"
IsHorizontalNavigationEnabled="{TemplateBinding IsHorizontalNavigationEnabled}"
x:Name="PART_mouseNavigation"/>
<d3:KeyboardNavigation IsVerticalNavigationEnabled="{TemplateBinding IsVerticalNavigationEnabled}"
IsHorizontalNavigationEnabled="{TemplateBinding IsHorizontalNavigationEnabled}"
x:Name="PART_keyboardNavigation"/>
<d3:VerticalContentControl d3:Figure.Placement="Left"
Content="{TemplateBinding LeftTitle}"
VerticalAlignment="Center"
IsTabStop="False"/>
<d3:PlotAxis x:Name="PART_verticalAxis"
d3:Figure.Placement="Left"
AxisOrientation="Left"
IsReversed = "{Binding IsYAxisReversed, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{TemplateBinding Foreground}"
Style="{StaticResource timeAxisStyle}">
<d3:MouseNavigation IsHorizontalNavigationEnabled="False"/>
</d3:PlotAxis>
<d3:AxisGrid x:Name="PART_axisGrid"
VerticalTicks="{Binding Ticks,ElementName=PART_verticalAxis, Mode=OneWay}"
HorizontalTicks="{Binding Ticks,ElementName=PART_horizontalAxis, Mode=OneWay}"
IsXAxisReversed = "{Binding IsXAxisReversed, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
IsYAxisReversed = "{Binding IsXAxisReversed, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Stroke="{TemplateBinding Foreground}" Opacity="0.25"/>
<ContentControl d3:Figure.Placement="Top"
HorizontalAlignment="Center"
FontSize="16"
Content="{TemplateBinding Title}"
Foreground="{TemplateBinding Foreground}"
IsTabStop="False"/>
<ContentControl d3:Figure.Placement="Bottom"
HorizontalAlignment="Center"
Content="{TemplateBinding BottomTitle}"
Foreground="{TemplateBinding Foreground}"
IsTabStop="False"/>
<d3:VerticalContentControl d3:Figure.Placement="Right"
Content="{TemplateBinding RightTitle}"
VerticalAlignment="Center"
IsTabStop="False"/>
<d3:PlotAxis x:Name="PART_horizontalAxis"
d3:Figure.Placement="Bottom"
AxisOrientation="Bottom"
IsReversed = "{Binding IsXAxisReversed, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{TemplateBinding Foreground}">
<d3:MouseNavigation IsVerticalNavigationEnabled="False"/>
</d3:PlotAxis>
<ContentPresenter/>
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding Foreground}" d3:Figure.Placement="Center"/>
<d3:Legend x:Name="PART_legend"
Foreground="Black" Content="{TemplateBinding LegendContent}"
Visibility="{TemplateBinding LegendVisibility}"/>
</d3:Figure>
<Rectangle x:Name="FocusVisualElement" RadiusX="2" RadiusY="2" Stroke="#FF6DBDD1" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsTabStop" Value="False"/>
</Style>
This is what I came up with, it's quite rough round the edges but should help you on your way. Your view XAML stays largely the same I've just added a button to start and stop things
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Click="Button_Click" Content="GO"/>
<d3:Chart Name="plotter" Grid.Row="1">
<d3:Chart.Title>
<TextBlock HorizontalAlignment="Center" FontSize="18" Margin="0,5,0,5">chart sample</TextBlock>
</d3:Chart.Title>
<d3:LineGraph x:Name="linegraph" Description="Simple linegraph" Stroke="Blue" StrokeThickness="3">
</d3:LineGraph>
</d3:Chart>
</Grid>
Your code behind then becomes
public partial class LiveView : Window
{
private const int DataPointsToShow = 100;
public Tuple<LinkedList<double>, LinkedList<double>> GraphData = new Tuple<LinkedList<double>, LinkedList<double>>(new LinkedList<double>(), new LinkedList<double>());
public Timer GraphDataTimer;
public LiveView()
{
InitializeComponent();
GraphDataTimer = new Timer(50);
GraphDataTimer.Elapsed += GraphDataTimer_Elapsed;
}
private void GraphDataTimer_Elapsed(object sender, ElapsedEventArgs e)
{
Random random = new Random();
if (GraphData.Item1.Count() > DataPointsToShow)
{
GraphData.Item1.RemoveFirst();
GraphData.Item2.RemoveFirst();
}
GraphData.Item1.AddLast(random.NextDouble()*200);
GraphData.Item2.AddLast(DateTime.Now.ToOADate());
Dispatcher.Invoke(() =>
{
linegraph.Plot(GraphData.Item1, GraphData.Item2);
});
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (GraphDataTimer.Enabled)
{
GraphDataTimer.Stop();
}
else
{
GraphDataTimer.Start();
}
}
}
Basically what it does it come up with a new value every 50 milliseconds and adds it to the end of the linked list. If the total number of points is above the number you want to display then it also removes the first one giving you a constantly scrolling graph with the most recent data at the top.
Related
I merged the things I have found out to implement an auto-scroll-to-end Headered Items Control. I cannot manage to do it. What am I doing wrong?
In Resource Directory, ScrollingHeaderedItemsControl is styled as:
<Style TargetType="common:ScrollingHeaderedItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type HeaderedItemsControl}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="3" SnapsToDevicePixels="True">
<StackPanel>
<Grid>
<Rectangle Fill="{TemplateBinding Background}"/>
<ContentPresenter ContentSource="Header" Margin="2,0,0,0"/>
</Grid>
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<ItemsPresenter Margin="5,0,0,0"/>
</ScrollViewer>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The class ScrollingHeaderedItemsControl is defined as here:
public class ScrollingHeaderedItemsControl : HeaderedItemsControl
{
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
int newItemCount = e.NewItems.Count;
if (newItemCount > 0)
this.ScrollToEnd();
base.OnItemsChanged(e);
}
}
}
ScrollToEnd is a static function written specifically for ScrollingHeaderedItemsControl such as:
public static void ScrollToEnd(this ItemsControl control)
{
try
{
Border border = VisualTreeHelper.GetChild((DependencyObject)control, 0) as Border;
StackPanel sp = VisualTreeHelper.GetChild((DependencyObject)border, 0) as StackPanel;
ScrollViewer sv = VisualTreeHelper.GetChild((DependencyObject)sp, 1) as ScrollViewer;
sv.ScrollToEnd();
}
catch(Exception)
{
}
}
ScrollingHeaderedItemsControl is used in the UserControl like this:
<common:ScrollingHeaderedItemsControl x:Name="MessagesHIC" FontSize="32" Header="Error/Warning/Info Messages"
Background="Green"
BorderBrush="AntiqueWhite" ItemsSource="{Binding Messages}"
Margin="10" Grid.Row="1" Grid.Column="0"
ScrollViewer.CanContentScroll="True">
<common:ScrollingHeaderedItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding MessageString}" Foreground="{Binding MessageColor}" TextWrapping="Wrap" FontWeight="Light" FontSize="26" />
</DataTemplate>
</common:ScrollingHeaderedItemsControl.ItemTemplate
</common:ScrollingHeaderedItemsControl>
Your template contains a stackpanel which doesn't set a height constraint, therefore the scrollviewer is never required to show the scrollbars. Change your Template to the following:
<Style TargetType="common:ScrollingHeaderedItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type HeaderedItemsControl}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="3" SnapsToDevicePixels="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid>
<Rectangle Fill="{TemplateBinding Background}" />
<ContentPresenter ContentSource="Header" Margin="2,0,0,0" />
</Grid>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Hidden">
<ItemsPresenter Margin="5,0,0,0" />
</ScrollViewer>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You will then need to change your code behind to:
try
{
Border border = VisualTreeHelper.GetChild((DependencyObject)control, 0) as Border;
Grid sp = VisualTreeHelper.GetChild((DependencyObject)border, 0) as Grid;
ScrollViewer sv = VisualTreeHelper.GetChild((DependencyObject)sp, 1) as ScrollViewer;
sv.ScrollToEnd();
}
catch (Exception)
{
}
I'm trying to bind custom textbox's BorderBrush/Foreground to its Text
The TextToBrush converter is called on Text change and the correct Brush is returned, but the actual foreground remains black and the actual border is not visible at all.
What am I doing wrong?
TextBox Style:
<Style TargetType="TextBox" x:Key="ScaleInputStyle">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="5*"/>
<RowDefinition Height="5*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="6*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.Background>
<ImageBrush ImageSource="{StaticResource ResourceKey=ScaleBgnd}" />
</Grid.Background>
<Border x:Name="InputBorder"
Grid.Row="1" Grid.Column="1"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5"
BorderBrush="{TemplateBinding BorderBrush}">
<TextBox x:Name="Input"
Text="{TemplateBinding Text}"
MaxLength="4"
BorderThickness="0"
BorderBrush="{x:Null}"
Foreground="{TemplateBinding Foreground}"
FontSize="40"
TextAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="Transparent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
TextBox:
<TextBox x:Name="ScaleInput"
Text="{Binding ElementName=PhotoDisplay, Path=PhotoImage.ScaleSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource TxtBoxToDoubleAndBack}}"
Style="{StaticResource ScaleInputStyle}"
Grid.Column="3" Grid.Row="4"
Foreground="{Binding Text, RelativeSource={RelativeSource Self}, Converter={StaticResource TextToBrush}, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}"
Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
BorderBrush="{Binding Text, RelativeSource={RelativeSource Self}, Converter={StaticResource TextToBrush}, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}"
BorderThickness="3"
IsEnabled="{Binding Path=ActionMode, ElementName=PhotoDisplay, Converter={StaticResource ActionToEnable}, ConverterParameter=EnableOnEvaluate, UpdateSourceTrigger=PropertyChanged}" />
Converter:
public class ConvertTextToBrush : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return Brushes.OrangeRed;
}
return Regex.IsMatch(value.ToString(), #"^((\d{1,2})(.\d)?)$", RegexOptions.IgnoreCase | RegexOptions.Singleline)
? Brushes.DodgerBlue
: Brushes.OrangeRed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
With the following code I can create a non wrapped scrolling stackpanel of headers of a tabcontrol. It has repeat buttons that can control the scroll. I would like instead for the repeat buttons to be able to control which tab is active, and adjust the scroll so the active tab can scroll into view. Is such a thing possible?
<TabControl SelectedItem="{Binding ActiveTicketFilterTab, Mode=TwoWay}"
Margin="5"
Grid.Row="1"
BorderBrush="Gray"
BorderThickness="2"
Style="{StaticResource ResourceKey=somestyle}"
SelectionChanged="selectionchanged"
Name="somename"
Background="#252525"
ItemsSource="{Binding someobslist}"
Grid.ColumnSpan="2">
<TabControl.Resources>
<Style x:Key="TabScrollerRepeatButtonStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="sc#1, 0.366693377, 0.372125238, 0.6931424">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding ContentControl.Content}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabControl.Template>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0"/>
<ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
<RowDefinition x:Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer Panel.ZIndex="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
<ScrollViewer.Style>
<Style TargetType="{x:Type ScrollViewer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Margin="0,0,0,0" Grid.Row="0" Grid.Column="0" x:Name="HeaderPanel">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollContentPresenter Grid.Column="0" Content="{TemplateBinding ScrollViewer.Content}" />
<StackPanel Orientation="Horizontal" Grid.Column="1">
<RepeatButton Style="{StaticResource TabScrollerRepeatButtonStyle}" Content="<" Command="ScrollBar.LineLeftCommand"/>
<RepeatButton Style="{StaticResource TabScrollerRepeatButtonStyle}" Content=">" Command="ScrollBar.LineRightCommand"/>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ScrollViewer.Style>
<StackPanel x:Name="HeaderPanel" Grid.Column="0" IsItemsHost="true" Margin="2,2,2,0" Grid.Row="0" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1" Orientation="Horizontal"/>
</ScrollViewer>
<Border x:Name="ContentPanel"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Grid.Row="1"
Grid.Column="0"
Margin="2,-2, 0, 13"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Local">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</Grid>
</ControlTemplate>
</TabControl.Template>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem" BasedOn="{StaticResource someotherstyle}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="{x:Null}" Height="25">
<TextBlock Padding="10" Name="Title" VerticalAlignment="Center" TextTrimming="CharacterEllipsis" HorizontalAlignment="Stretch" Text="{Binding Name}" ToolTip="{Binding Name}" />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid Background="Transparent">
<ListBox x:Name="ticketView"
Unloaded="On_Listbox_enter"
ItemsSource="{Binding TicketsView}"
Background="#252525"
Margin="5,5,0,0"
HorizontalContentAlignment="Stretch"
BorderBrush="{x:Null}"
BorderThickness="1"
Padding="0,0,5,0"
VirtualizingPanel.IsVirtualizing="False"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="False"
ItemTemplate="{StaticResource TicketViewTemplate}">
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#252525" />
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent" />
</ListBox.Resources>
</ListBox>
<StackPanel Grid.Row="0" Margin="328,75"
Visibility="{Binding ExposeTicketSpinner, Converter={StaticResource BoolToVisConverter}}"
Width="Auto">
<Viewbox Grid.Row="0"
Height="100"
Width="100"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<WPFControls:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center" />
</Viewbox>
<Label Foreground="White" FontWeight="Bold" Margin="0,33" Content="Loading Tickets..." HorizontalAlignment="Center"></Label>
</StackPanel>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
To get your things to work as desired, do the following:
First
Replace your Repeatbuttons with this
<RepeatButton Style="{StaticResource TabScrollerRepeatButtonStyle}" Content="<" Command="{Binding MoveLeftCommand}"/>
<RepeatButton Style="{StaticResource TabScrollerRepeatButtonStyle}" Content=">" Command="{Binding MoveRightCommand}"/>
Second
In your ViewModel (That holds the TabControl) do it like this:
public ICommand MoveLeftCommand => new RelayCommand(x => {
var currIdx = this.someobslist.IndexOf(this.ActiveTicketFilterTab);
this.ActiveTicketFilterTab = currIdx == 0 ? this.someobslist[this.someobslist.Count - 1] : this.someobslist[currIdx - 1];
});
public ICommand MoveRightCommand => new RelayCommand(x => {
var currIdx = this.someobslist.IndexOf(this.ActiveTicketFilterTab);
this.ActiveTicketFilterTab = currIdx == this.someobslist.Count - 1 ? this.someobslist[0] : this.someobslist[currIdx + 1];
});
Thats it. In case you need a RelayCommand, i can hand you mine. Of course replace my Varaibles with yours.
This Solution circles now through the TabItems
EDIT
Here is a plain scrollcycler without navigating to the TabItem, based on this <--- IMPORTANT
Add to your ScrollViewer a Loaded-Event in XAML
Here the code:
private int _scollMoverIdx = 0;
private TabControl _container;
private void ScrollViewer_OnLoaded(object sender, RoutedEventArgs e) {
this._container = (sender as ScrollViewer).TemplatedParent as TabControl;
}
public ICommand MoveLeftCommand => new RelayCommand(x => {
if (this._scollMoverIdx == 0) {
this._scollMoverIdx = this.someobslist.Count - 1;
this._container.ScrollToCenterOfView(this.someobslist[_scollMoverIdx]);
} else {
_scollMoverIdx--;
this._container.ScrollToCenterOfView(this.someobslist[_scollMoverIdx]);
}
});
public ICommand MoveRightCommand => new RelayCommand(x => {
if (this._scollMoverIdx == this.someobslist.Count - 1) {
this._scollMoverIdx = 0;
this._container.ScrollToCenterOfView(this.someobslist[_scollMoverIdx]);
} else {
_scollMoverIdx++;
this._container.ScrollToCenterOfView(this.someobslist[_scollMoverIdx]);
}
});
Its not really pretty, but i wanted to keep it simple. The linked Extension method is greate for reusability (Credits to Ray Burns). If you want this to be MVVM-conform, i suggest making a CustomControl, since scrolling from ViewModel is breaking the pattern.
Cheers
I need a thumb control that can be sized using a mouse. When the user hovers the mouse over one of the ends a size cursor should be displayed and when the user clicks and drags the end of the control it will be re-sized.
How can that be achieved?
Here is one I made a while ago, it allows Move and Resize, but you can remove the Move logic and it should work fine (the style is still a bit messy, but it works pretty well)
Its based on ContentControl so you can add any Element inside and Move/Resize on a Canvas, It uses 3 Adorners, one for Resize, one for Move and one to display information (current size)
Here is a full working example if you want to test/use/modify/improve :)
Code:
namespace WpfApplication21
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class ResizeThumb : Thumb
{
public ResizeThumb()
{
DragDelta += new DragDeltaEventHandler(this.ResizeThumb_DragDelta);
}
private void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Control designerItem = this.DataContext as Control;
if (designerItem != null)
{
double deltaVertical, deltaHorizontal;
switch (VerticalAlignment)
{
case VerticalAlignment.Bottom:
deltaVertical = Math.Min(-e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
designerItem.Height -= deltaVertical;
break;
case VerticalAlignment.Top:
deltaVertical = Math.Min(e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
Canvas.SetTop(designerItem, Canvas.GetTop(designerItem) + deltaVertical);
designerItem.Height -= deltaVertical;
break;
default:
break;
}
switch (HorizontalAlignment)
{
case HorizontalAlignment.Left:
deltaHorizontal = Math.Min(e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
Canvas.SetLeft(designerItem, Canvas.GetLeft(designerItem) + deltaHorizontal);
designerItem.Width -= deltaHorizontal;
break;
case HorizontalAlignment.Right:
deltaHorizontal = Math.Min(-e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
designerItem.Width -= deltaHorizontal;
break;
default:
break;
}
}
e.Handled = true;
}
}
public class MoveThumb : Thumb
{
public MoveThumb()
{
DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);
}
private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Control designerItem = this.DataContext as Control;
if (designerItem != null)
{
double left = Canvas.GetLeft(designerItem);
double top = Canvas.GetTop(designerItem);
Canvas.SetLeft(designerItem, left + e.HorizontalChange);
Canvas.SetTop(designerItem, top + e.VerticalChange);
}
}
}
public class SizeAdorner : Adorner
{
private Control chrome;
private VisualCollection visuals;
private ContentControl designerItem;
protected override int VisualChildrenCount
{
get
{
return this.visuals.Count;
}
}
public SizeAdorner(ContentControl designerItem)
: base(designerItem)
{
this.SnapsToDevicePixels = true;
this.designerItem = designerItem;
this.chrome = new Control();
this.chrome.DataContext = designerItem;
this.visuals = new VisualCollection(this);
this.visuals.Add(this.chrome);
}
protected override Visual GetVisualChild(int index)
{
return this.visuals[index];
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
this.chrome.Arrange(new Rect(new Point(0.0, 0.0), arrangeBounds));
return arrangeBounds;
}
}
}
Xaml:
<Window x:Class="WpfApplication21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication21"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<local:MoveThumb Cursor="SizeAll">
<local:MoveThumb.Style>
<Style TargetType="{x:Type local:MoveThumb}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MoveThumb}">
<Rectangle Fill="Transparent" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:MoveThumb.Style>
</local:MoveThumb>
<Control x:Name="resizer">
<Control.Style>
<Style TargetType="{x:Type Control}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<Grid>
<Grid Opacity="0" Margin="-3">
<local:ResizeThumb Height="3" Cursor="SizeNS" VerticalAlignment="Top" HorizontalAlignment="Stretch"/>
<local:ResizeThumb Width="3" Cursor="SizeWE" VerticalAlignment="Stretch" HorizontalAlignment="Left"/>
<local:ResizeThumb Width="3" Cursor="SizeWE" VerticalAlignment="Stretch" HorizontalAlignment="Right"/>
<local:ResizeThumb Height="3" Cursor="SizeNS" VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/>
<local:ResizeThumb Width="7" Height="7" Margin="-2" Cursor="SizeNWSE" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<local:ResizeThumb Width="7" Height="7" Margin="-2" Cursor="SizeNESW" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<local:ResizeThumb Width="7" Height="7" Margin="-2" Cursor="SizeNESW" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<local:ResizeThumb Width="7" Height="7" Margin="-2" Cursor="SizeNWSE" VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
</Grid>
<Grid IsHitTestVisible="False" Opacity="1" Margin="-3">
<Grid.Resources>
<Style TargetType="{x:Type Ellipse}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Stroke" Value="#FFC8C8C8" />
<Setter Property="StrokeThickness" Value=".5" />
<Setter Property="Width" Value="7" />
<Setter Property="Height" Value="7" />
<Setter Property="Margin" Value="-2" />
<Setter Property="Fill" Value="Silver" />
</Style>
</Grid.Resources>
<Rectangle SnapsToDevicePixels="True" StrokeThickness="1" Margin="1" Stroke="Black" StrokeDashArray="4 4"/>
<Ellipse HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Ellipse HorizontalAlignment="Right" VerticalAlignment="Top"/>
<Ellipse HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
<Ellipse HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Control.Style>
</Control>
<Grid x:Name="sizeInfo" SnapsToDevicePixels="True">
<Path Stroke="Red" StrokeThickness="1" Height="10" VerticalAlignment="Bottom" Margin="-2,0,-2,-15" Stretch="Fill" Data="M0,0 0,10 M 0,5 100,5 M 100,0 100,10"/>
<TextBlock Text="{Binding Width}" Background="White" Padding="3,0,3,0" Foreground="Red" Margin="0,0,0,-18" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
<Path Stroke="Red" StrokeThickness="1" Width="10" HorizontalAlignment="Right" Margin="0,-2,-15,-2" Stretch="Fill" Data="M5,0 5,100 M 0,0 10,0 M 0,100 10,100"/>
<TextBlock Text="{Binding Height}" Background="White" Foreground="Red" Padding="3,0,3,0" Margin="0,0,-18,0" HorizontalAlignment="Right" VerticalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="90" CenterX="1" CenterY="0.5"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Grid>
<ContentPresenter Content="{TemplateBinding Content}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="sizeInfo" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter TargetName="sizeInfo" Property="Visibility" Value="Hidden" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Canvas>
<ContentControl Width="183" Height="110" Canvas.Left="166" Canvas.Top="50" />
</Canvas>
</Window>
Result:
With content inside (Button)
Sorry the cursors do not show when using SnipTool
So I'm at a loss to understand this. I have a custom control that uses up/down buttons to increase/decrease the time as displayed on toggle buttons. I have the Content property of the Toggles displaying the correct values of the Hour and Minute properties, and I'm now trying to set up the code-behind to increase the values. According to the VS2010 debugger, it is increasing the value of the Hour property, but it's not changing the content of the Toggles to reflect this. I have the binding mode set to TwoWay, and I'm using {Binding RelativeSource={RelativeSource TemplatedParent}, Path=Hour, Mode=TwoWay} to bind to the value as this is within the generic.xaml file of the custom control. Any ideas on how to make the displayed value update correctly?
XAML: (Style templates removed to save space)
<Style TargetType="{x:Type local:TimePicker}">
<Setter Property="Height" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}" />
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="Transparent"
BorderThickness="1">
<StackPanel x:Name="PART_Root"
Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<!--Region Hour Button-->
<ToggleButton x:Name="PART_Hour"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Margin="0"
BorderBrush="Transparent"
BorderThickness="0"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Hour, Mode=TwoWay, BindsDirectlyToSource=True}">
</ToggleButton>
<!--EndRegion-->
<Label HorizontalContentAlignment="{TemplateBinding HorizontalAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalAlignment}"
FontSize="14"
Content=":"/>
<!--Region Minute Button-->
<ToggleButton x:Name="PART_Minute"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Margin="0"
BorderBrush="Transparent"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Minute}">
</ToggleButton>
<!--EndRegion-->
<StackPanel x:Name="PART_IncDecPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Vertical">
<Grid Height="{Binding ElementName=PART_Hour, Path=ActualHeight}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Viewbox Stretch="Fill" Grid.Row="0">
<!--Region Increase Button-->
<Button x:Name="PART_IncreaseTime"
HorizontalContentAlignment="{TemplateBinding HorizontalAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalAlignment}"
BorderBrush="Transparent"
BorderThickness="0"
FontFamily="Marlett"
Foreground="DimGray"
Content="5"
Padding="0"
Click="PART_IncreaseTime_Click">
</Button>
<!--EndRegion-->
</Viewbox >
<Viewbox Stretch="Fill" Grid.Row="1">
<!--Region Decrease Button-->
<Button x:Name="PART_DecreaseTime"
HorizontalContentAlignment="{TemplateBinding HorizontalAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalAlignment}"
BorderBrush="Transparent"
BorderThickness="0"
FontFamily="Marlett"
Foreground="DimGray"
Content="6"
Padding="0">
</Button>
<!--EndRegion-->
</Viewbox>
</Grid>
</StackPanel>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Code:
public class TimePicker : Control
{
#region Dependency Property Declarations
public static DependencyProperty HourProperty = DependencyProperty.Register("Hour", typeof(int), typeof(TimePicker),
new FrameworkPropertyMetadata((int)12, new PropertyChangedCallback(OnHourChanged)));
public static DependencyProperty MinuteProperty = DependencyProperty.Register("Minute", typeof(string), typeof(TimePicker),
new FrameworkPropertyMetadata((string)"00", new PropertyChangedCallback(OnMinuteChanged)));
#endregion
#region Properties
public int Hour
{
get { return (int)GetValue(HourProperty); }
set { SetValue(HourProperty, value); }
}
public string Minute
{
get { return (string)GetValue(MinuteProperty); }
set { SetValue(MinuteProperty, value); }
}
#endregion
#region Events
private static void OnHourChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
TimePicker time = new TimePicker();
time.Hour = (int)e.NewValue;
MessageBox.Show("Hour changed");
}
private static void OnMinuteChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
}
#endregion
static TimePicker()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TimePicker), new FrameworkPropertyMetadata(typeof(TimePicker)));
}
}
public partial class TimePickerEvents : ResourceDictionary
{
TimePicker time = new TimePicker();
void PART_IncreaseTime_Click(object sender, RoutedEventArgs e)
{
time.Hour += 1;
}
}
Not sure why you are using a Togglebutton to display the hour/minutes, I would think a Label/TextBlock would be better suited for that.
Change the binding of your ToggleButtons to
Content="{TemplateBinding Hour}"
Then in the code, override OnApplyTemplate as such
public override void OnApplyTemplate()
{
var upButton = GetTemplateChild("PART_IncreaseTime") as Button;
upButton.Click += IncreaseClick;
var downButton = GetTemplateChild("PART_DecreaseTime") as Button;
downButton.Click += DecreaseClick;
}
private void IncreaseClick(object sender, RoutedEventArgs e)
{
// Here would be a place to see what toggle button is checked
// (or which TextBlock last had focus) and increase Hour/Minute
// based on that info
Hour += 1;
}
private void DecreaseClick(object sender, RoutedEventArgs e)
{
// Here would be a place to see what toggle button is checked
// (or which TextBlock last had focus) and decreaseHour/Minute
// based on that info
Hour -= 1;
}
This should get you started.
FYI: Within your OnHourChanged and OnMinuteChanged the sender is your control (TimePicker). So you can cast the sender to TimePicker and access all of your properties. Even your private properties.
Why don't you use the WPF toolkit's numerical up down control?
http://wpftoolkit.codeplex.com/wikipage?title=NumericUpDown
All you have to do is bind it to your properties two way.