Location>code7788 >text

WPF [Infinite Scroll Image Viewer] Custom Controls

Popularity:695 ℃/2024-08-04 09:08:15

Custom Controls

image

Customizing controls is a topic that is relatively new to me. I haven't practiced wpf in a while and need to consolidate my memory. I thought about it for a while, opened up Anime House, and suddenly thought this image viewing control for watching manga was interesting. So I spent a special day making this image control. I thought it would be easy, but it's not. This picture viewing control is much more difficult than I thought, there are technical difficulties, there are also logical difficulties. Well, it all worked out in the end.

This custom control is kind of a good exercise. It involves thedependent property Binding Virtualization Loading VisualState Dynamic resources transition effect character radical cursor (computing)These things.

Cursor production

For the computer side, a good looking cursor is essential. So I looked up the information and learned about a relatively simple way to customize the cursor. It's to make a cursor file and then create a customized cursor in theXAMLThe direct use in the

<Button x:Name="P_Bleft" Opacity="0" ="0" Cursor="/Programset;component/Path/">Previous</Button>
<Button x:Name="P_Bright" Opacity="0" = "1" Cursor="/programset;component/path/">Next</Button>

Where to find the icon? I remembered.FontAwsome.V5, this is very convenient and aesthetically pleasing. So I went to the official website and found two arrow icons. The next step was to convert the icons into a cursor file.FontAwsome.V5offered to copy icons in svg format, and then I found this site at-->Online converterIt is possible to convert svg to cur cursor file and it offers 10 free credits per day. But be careful, you need to set the cursor size. It can be set that after saving as a file, the svg element'sWidthcap (a poem)HeightThe attribute is changed to 16.

<svg xmlns="http:///2000/svg" viewBox="0 0 512 512" width="16" height="16">

image

Finally put the cursor file into the project to generate theresource (such as manpower or tourism)form of compilation. Here it can be used directly in XAML. As shown earlier, it is a black arrow.

control panel

As an image viewing control, a control panel is essential. The more classic approach isClick on the center to show panel help. Click on the sides to switch images. I'll leave out the panel help for now and just do the switching of images. The entire custom control is aGrid, divided into 2 columns, then 1 column for each of the two buttons, then hide the buttons. Luckily, the event is still captured when the transparency is 0. Below that, overlap a couple more widths of 2 columnsImagecontrol as a viewport.

image

control structure

After many changes, I ended up designing this custom control as a 4-tier control.Z-Indexcontrol.

  • Z-Index=4 Previous, Next
  • Z-Index=3 Current Page
  • Z-Index=2 Next page according to sliding direction
  • Z-Index=1 Separator between previous and next Z-Index page
  • Z-Index=0 Previous page according to sliding direction
  • Bottom Container Grid

I added this layer by layer during the debugging process to achieve the normal display. But there's no way to give a nonCanvasSubcontrols set thisadditional property, so this additional property can only be set dynamically in the code of the logical control. This setting code is interwoven with the control interaction logic and is itself part of the control logic.

var P_Bleft = (Button)GetTemplateChild("P_Bleft");
P_Bleft.SetValue(, 4);
P_Bright.SetValue(, 4);
P_Bulkhead.SetValue(, 1);

Virtualization Loading

As an image viewer, loading all the images at once is a convenient option, but not a wise one. So I considered theVirtualization Loading. At first I thought of collection controlsListViewBut this one I need a transition effect. But this one I need the transition effect. The collection control doesn't seem to implement this. And the content size control is not perfect. I'll just start from 0 myself.

I went through a lot of detours and finally settled on using 3 horizontally alignedImagecontrol for image rendering. The image is rendered using theStoryboardThe timeline controls the transition effect. I organized the collection of image URLs into a ring and then had the 3ImageMove around the ring so that virtualized loading is achieved.

The first is to preload the current page and the next page. After clicking on the next page, the image control moves to the left, while the leftmost, moving out of the viewportImageThe control loads the next next page. On the second click, the first preloaded image is moved inside the viewport, and then the second preloaded image is moved to the next page position. On the third click, this control moves inside the viewport, and a similar process is repeated for each control in a loop. There is also the direction to consider, i.e. clicking on the previous page. So this has to be handled with judgment in the logic code.

I used a simplemodulo operator (math.)Organize data into rings

// Current page
int state = (Current) % 3;

Then in aswitchCompleting the control logic in the structure

switch (state)
{
    case 0.
//lots of judgment
break.
    case 1.
//many judgments
case 1: //lots of judgments break; case 2: //lots of judgments break
    case 2.
//many judgments
break; }
}

existswichA few important tasks in the structure are

  • Make sure to picture those 3ImageWhere the control is to be moved from, I choose the efficientRenderTransform. Position one has 3-width、0、widthEach control occupies a position at each moment in time. Each control occupies a position at each moment in time.
  • Calculate the correct value of the Z-Index for each control at different times. The hierarchy has already been pointed out earlier.
  • Find the image that needs to be loaded, and at the right time switch a certainImageThe image source for the control. After banging my head against the wall, I realized that replacing them all at the same time was a mistake. Finally switched to loading one image per click value. I.e. load the next one depending on the direction of the slide. But be careful to determine if the array index is exceeded when necessary.
//shift left
P_Cimage.Source = new BitmapImage(new Uri(Urls[Current - 1]));
//right shift
if(Current + 1 < )
{
    P_Cimage.Source = new BitmapImage(new Uri(Urls[Current + 1]));
}

utilizationVisualStateIt is possible to simplify this management. I designed this switching as 3 statesNormal R_Translate L_Translate. Each of the 3 picture controls occupies a different position at different states and then switches states according to the ring structure of the data organization.

The problem here is that there is no way to bind the animation in the state to the dependent property, and secondly there is no way to use theDynamic resourcesPerform switching of attribute values. Or do you get to control it in the logic code.

P_BL.(-P_BC.ActualWidth, 0);
P_BC.(0, 0);
P_BR.(P_BC.ActualWidth, 0);

d4_.To = -P_BC.ActualWidth;
d5_.To = 0;
d6_.To = P_BC.ActualWidth;

 = 0;
 = P_BC.ActualWidth;
 = -P_BC.ActualWidth;

In order for the animated element to reach the desired offset position in response to a change in the size of the control, it must be animated before each click.trimmingcontrol location. To make theImageelementalRenderTransformutilizationDynamicResource,to be able to update the position in the code. Click next page after full screen, next pageImageThe offset of the control is1920, but after solving for fullscreen, the offset needed is much smaller and needs to be calculated dynamically based on the size of the control. The main reason for this is because animation is going to be used here, otherwise using layout calculations might be a better way to go.

<Border x:Name="P_BL" ="0" ="2" Background="LightPink">
    <>
        <TranslateTransform X="{DynamicResource left}"/>
    </>
    <Image x:Name="P_Limage"/>
</Border>

Full Code

<ResourceDictionary
    xmlns="/winfx/2006/xaml/presentation"
    xmlns:x="/winfx/2006/xaml"
    xmlns:local="clr-namespace:Pictures" 
    xmlns:sys="clr-namespace:System;assembly=mscorlib" 
    xmlns:sys2="/netfx/2009/xaml/presentation">
    
    <!--<local:NegativeValueConverter x:Key="NegativeValueConverter"/>-->
    <sys:Double x:Key="left">-1200</sys:Double>
    <sys:Double x:Key="right">1200</sys:Double>
    <sys2:Duration x:Key="dur">0:0:0.2</sys2:Duration>
    <sys2:PowerEase EasingMode="EaseOut" Power="0.3" x:Key="ea"/>


    <Style TargetType="{x:Type local:CustomControl2}">
        <Setter Property="Template">
            <>
                <ControlTemplate TargetType="{x:Type local:CustomControl2}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid>
                            <>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                            </>
                            <Border x:Name="P_Bulkhead" ="0" ="2" Background="White"></Border>
                            <Border x:Name="P_BC" ="0" ="2" Background="LightGreen">
                                <>
                                    <!--This is always zero and does not require the use of resources-->
                                    <TranslateTransform X="0"/>
                                </>
                                <Image x:Name="P_Cimage"/>
                            </Border>
                            <Border x:Name="P_BL" ="0" ="2" Background="LightPink">
                                <>
                                    <TranslateTransform X="{DynamicResource left}"/>
                                </>
                                <Image x:Name="P_Limage"/>
                            </Border>
                            <Border x:Name="P_BR" ="0" ="2" Background="LightBlue">
                                <>
                                    <TranslateTransform X="{DynamicResource right}"/>
                                </>
                                <Image x:Name="P_Rimage"/>
                            </Border>
                            <TextBlock ="2" Text="{Binding Current, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}{0}'}"/>
                            <Button x:Name="P_Bleft" Opacity="0" ="0" Cursor="/Pictures;component/images/">preceding page</Button>
                            <Button x:Name="P_Bright" Opacity="0" ="1" Cursor="/Pictures;component/images/">next page</Button>

                        </Grid>
                        <>
                            <VisualStateGroup x:Name="group1">
                                <VisualState x:Name="Normal">
                                    <Storyboard>
                                        <DoubleAnimation x:Name="d1_" ="P_BL" ="().()" Duration="0:0:0" To="{DynamicResource left}"/>
                                        <DoubleAnimation x:Name="d2_" ="P_BC" ="().()" Duration="0:0:0" To="0"/>
                                        <DoubleAnimation x:Name="d3_" ="P_BR" ="().()" Duration="0:0:0" To="{DynamicResource right}"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="R_Translate">
                                    <Storyboard>
                                        <DoubleAnimation x:Name="d4_" ="P_BC" ="().()" Duration="0:0:0" To="{DynamicResource left}"/>
                                        <DoubleAnimation x:Name="d5_" ="P_BR" ="().()" Duration="0:0:0" To="0"/>
                                        <DoubleAnimation x:Name="d6_" ="P_BL" ="().()" Duration="0:0:0" To="{DynamicResource right}"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="L_Translate">
                                    <Storyboard>
                                        <DoubleAnimation x:Name="d7_" ="P_BR" ="().()" Duration="0:0:0" To="{DynamicResource left}"/>
                                        <DoubleAnimation x:Name="d8_" ="P_BL" ="().()" Duration="0:0:0" To="0"/>
                                        <DoubleAnimation x:Name="d9_" ="P_BC" ="().()" Duration="0:0:0" To="{DynamicResource right}"/>
                                    </Storyboard>
                                </VisualState>
                                <>
                                    <VisualTransition To="Normal">
                                        <Storyboard>
                                            <DoubleAnimation x:Name="d1" ="P_BL" EasingFunction="{StaticResource ea}" ="().()" Duration="{StaticResource dur}" To="{DynamicResource left}"/>
                                            <DoubleAnimation x:Name="d2" ="P_BC" EasingFunction="{StaticResource ea}" ="().()" Duration="{StaticResource dur}" To="0"/>
                                            <DoubleAnimation x:Name="d3" ="P_BR" EasingFunction="{StaticResource ea}" ="().()" Duration="{StaticResource dur}" To="{DynamicResource right}"/>
                                        </Storyboard>
                                    </VisualTransition>
                                    <VisualTransition To="R_Translate">
                                        <Storyboard>
                                            <DoubleAnimation x:Name="d4" ="P_BC" EasingFunction="{StaticResource ea}" ="().()" Duration="{StaticResource dur}" To="{DynamicResource left}"/>
                                            <DoubleAnimation x:Name="d5" ="P_BR" EasingFunction="{StaticResource ea}" ="().()" Duration="{StaticResource dur}" To="0"/>
                                            <DoubleAnimation x:Name="d6" ="P_BL" EasingFunction="{StaticResource ea}" ="().()" Duration="{StaticResource dur}" To="{DynamicResource right}"/>
                                        </Storyboard>
                                    </VisualTransition>
                                    <VisualTransition To="L_Translate">
                                        <Storyboard>
                                            <DoubleAnimation x:Name="d7" ="P_BR" EasingFunction="{StaticResource ea}" ="().()" Duration="{StaticResource dur}" To="{DynamicResource left}"/>
                                            <DoubleAnimation x:Name="d8" ="P_BL" EasingFunction="{StaticResource ea}" ="().()" Duration="{StaticResource dur}" To="0"/>
                                            <DoubleAnimation x:Name="d9" ="P_BC" EasingFunction="{StaticResource ea}" ="().()" Duration="{StaticResource dur}" To="{DynamicResource right}"/>
                                        </Storyboard>
                                    </VisualTransition>
                                </>
                            </VisualStateGroup>
                        </>
                    </Border>
                </ControlTemplate>
            </>
        </Setter>
    </Style>
</ResourceDictionary>

Backend Code

namespace Pictures
{
    [TemplatePart(Name = "P_Bleft",Type = typeof(Button))]
    [TemplatePart(Name = "P_Bright", Type = typeof(Button))]
    [TemplatePart(Name = "P_Limage", Type = typeof(Image))]
    [TemplatePart(Name = "P_Cimage", Type = typeof(Image))]
    [TemplatePart(Name = "P_Rimage", Type = typeof(Image))]
    [TemplatePart(Name = "P_BC", Type = typeof(Border))]
    [TemplatePart(Name = "P_BL", Type = typeof(Border))]
    [TemplatePart(Name = "P_BR", Type = typeof(Border))]
    [TemplatePart(Name = "P_Bulkhead",Type =typeof(Border))]
    [TemplatePart(Name = "P_BC", Type = typeof(Border))]
    [TemplatePart(Name = "P_BL", Type = typeof(Border))]
    [TemplatePart(Name = "P_BR", Type = typeof(Border))]
    [TemplateVisualState(GroupName = "group1",Name = "L_Translate")]
    [TemplateVisualState(GroupName = "group1", Name = "R_Translate")]
    [TemplateVisualState(GroupName = "group1", Name = "Normal")]
    [TemplatePart(Name = "d1", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d2", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d3", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d4", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d5", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d6", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d7", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d8", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d9", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d1_", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d2_", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d3_", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d4_", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d5_", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d6_", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d7_", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d8_", Type = typeof(DoubleAnimation))]
    [TemplatePart(Name = "d9_", Type = typeof(DoubleAnimation))]
    public class CustomControl2 : Control
    {
        public const string L_Translate = "L_Translate";
        public const string R_Translate = "R_Translate";
        public const string Normal = "Normal";
        static CustomControl2()
        {
            (typeof(CustomControl2), new FrameworkPropertyMetadata(typeof(CustomControl2)));
        }

        /// <summary>
        /// current page
        /// </summary>
        public int Current
        {
            get { return (int)GetValue(CurrentProperty); }
            set { SetValue(CurrentProperty, value); }
        }

        public static readonly DependencyProperty CurrentProperty =
            ("Current", typeof(int), typeof(CustomControl2), new PropertyMetadata(0));

        /// <summary>
        /// photograph
        /// </summary>
        public ObservableCollection<string> Urls
        {
            get { return (ObservableCollection<string>)GetValue(UrlsProperty); }
            set { SetValue(UrlsProperty, value); }
        }
        public static readonly DependencyProperty UrlsProperty =
            ("Urls", typeof(ObservableCollection<string>), typeof(CustomControl2), new PropertyMetadata(null, OnUrlsChanged));
        private static void OnUrlsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CustomControl2 control = d as CustomControl2;
            if (control != null)
            {
                ((ObservableCollection<string>), (ObservableCollection<string>));
            }
        }

        private void OnUrlsChanged(ObservableCollection<string> oldValue, ObservableCollection<string> newValue)
        {
            Current = 0;
            BeginStoryBoard(0, true);
        }

        public override void OnApplyTemplate()
        {
            ();
            Button P_Bleft = (Button)GetTemplateChild("P_Bleft");
            Button P_Bright = (Button)GetTemplateChild("P_Bright");
            //partitions
            Border P_Bulkhead = (Border)GetTemplateChild("P_Bulkhead");
            P_Bleft.SetValue(, 4);
            P_Bright.SetValue(, 4);
            P_Bulkhead.SetValue(, 1);
            P_Bleft.Click += (s, e) => ToPage(-1);
            P_Bright.Click += (s, e) => ToPage(1);
        }

        private void ToPage(int offset)
        {
            if (Current==0 && offset<0)
            {
                return;
            }
            if (Current>=-1 && offset>0)
            {
                return;
            }
            Current += offset;

            BeginStoryBoard(offset);
        }

        private void BeginStoryBoard(int offset,bool init=false)
        {
            Image P_Cimage = (Image)GetTemplateChild("P_Cimage");
            Image P_Limage = (Image)GetTemplateChild("P_Limage");
            Image P_Rimage = (Image)GetTemplateChild("P_Rimage");
            Border P_BC = (Border)GetTemplateChild("P_BC");
            Border P_BL = (Border)GetTemplateChild("P_BL");
            Border P_BR = (Border)GetTemplateChild("P_BR");
            ["left"]= -P_BC.ActualWidth;
            ["right"] = P_BC.ActualWidth;
            //current page
            int state = (Current) % 3;

            //Since animations cannot be bound toTocausality,Can only be dynamically adjusted in code
            #region Adjustment status
            DoubleAnimation d1 = (DoubleAnimation)GetTemplateChild("d1");
            DoubleAnimation d2 = (DoubleAnimation)GetTemplateChild("d2");
            DoubleAnimation d3 = (DoubleAnimation)GetTemplateChild("d3");
            DoubleAnimation d4 = (DoubleAnimation)GetTemplateChild("d4");
            DoubleAnimation d5 = (DoubleAnimation)GetTemplateChild("d5");
            DoubleAnimation d6 = (DoubleAnimation)GetTemplateChild("d6");
            DoubleAnimation d7 = (DoubleAnimation)GetTemplateChild("d7");
            DoubleAnimation d8 = (DoubleAnimation)GetTemplateChild("d8");
            DoubleAnimation d9 = (DoubleAnimation)GetTemplateChild("d9");
            DoubleAnimation d1_ = (DoubleAnimation)GetTemplateChild("d1_");
            DoubleAnimation d2_ = (DoubleAnimation)GetTemplateChild("d2_");
            DoubleAnimation d3_ = (DoubleAnimation)GetTemplateChild("d3_");
            DoubleAnimation d4_ = (DoubleAnimation)GetTemplateChild("d4_");
            DoubleAnimation d5_ = (DoubleAnimation)GetTemplateChild("d5_");
            DoubleAnimation d6_ = (DoubleAnimation)GetTemplateChild("d6_");
            DoubleAnimation d7_ = (DoubleAnimation)GetTemplateChild("d7_");
            DoubleAnimation d8_ = (DoubleAnimation)GetTemplateChild("d8_");
            DoubleAnimation d9_ = (DoubleAnimation)GetTemplateChild("d9_");
             = -P_BC.ActualWidth;
             = 0;
             = P_BC.ActualWidth;
             = -P_BC.ActualWidth;
             = 0;
             = P_BC.ActualWidth;
             = -P_BC.ActualWidth;
             = 0;
             = P_BC.ActualWidth;
            d1_.To = -P_BC.ActualWidth;
            d2_.To = 0;
            d3_.To = P_BC.ActualWidth;
            d4_.To = -P_BC.ActualWidth;
            d5_.To = 0;
            d6_.To = P_BC.ActualWidth;
            d7_.To = -P_BC.ActualWidth;
            d8_.To = 0;
            d9_.To = P_BC.ActualWidth;
            #endregion

            switch (state)
            {
                case 0:
                    P_BC.SetValue(, 3);
                    if (init)
                    {
                        if (>0)
                        {
                            P_Cimage.Source = new BitmapImage(new Uri(Urls[0]));
                        }
                        if ( > 1)
                        {
                            P_Rimage.Source = new BitmapImage(new Uri(Urls[1]));
                        }
                        P_BL.(-P_BC.ActualWidth, 0);
                        P_BC.(0, 0);
                        P_BR.(P_BC.ActualWidth, 0);
                    }
                    else
                    {
                        if (offset==1)
                        {
                            //right shift
                            if(Current + 1 < )
                            {
                                P_Rimage.Source = new BitmapImage(new Uri(Urls[Current + 1]));
                            }
                            P_BL.SetValue(, 2);
                            P_BR.SetValue(, 0);
                             = 0;
                             = P_BC.ActualWidth;
                             = -P_BC.ActualWidth;
                        }
                        if (offset==-1)
                        {
                            //shift left
                            if (Current>0)
                            {
                                P_Limage.Source = new BitmapImage(new Uri(Urls[Current - 1]));
                            }
                            P_BL.SetValue(, 0);
                            P_BR.SetValue(, 2);
                             = P_BC.ActualWidth;
                             = -P_BC.ActualWidth;
                             = 0;
                        }
                    }
                    (this, Normal, true);
                    break;
                case 1:
                    P_BR.SetValue(, 3);
                    if (offset == 1)
                    {
                        //right shift
                        if (Current+1<)
                        {
                            P_Limage.Source = new BitmapImage(new Uri(Urls[Current + 1]));
                        }
                        P_BC.SetValue(, 2);
                        P_BL.SetValue(, 0);
                         = 0;
                         = P_BC.ActualWidth;
                         = -P_BC.ActualWidth;
                    }
                    if (offset == -1)
                    {
                        //shift left
                        P_Cimage.Source = new BitmapImage(new Uri(Urls[Current - 1]));
                        P_BC.SetValue(, 0);
                        P_BL.SetValue(, 2);
                         = P_BC.ActualWidth;
                         = -P_BC.ActualWidth;
                         = 0;
                    }
                    (this, R_Translate, true);
                    break;
                case 2:
                    P_BL.SetValue(, 3);
                    if (offset == 1)
                    {
                        //right shift
                        if(Current + 1 < )
                        {
                            P_Cimage.Source = new BitmapImage(new Uri(Urls[Current + 1]));
                        }
                        P_BC.SetValue(, 0);
                        P_BR.SetValue(, 2);
                        =0;
                         = P_BC.ActualWidth;
                        =-P_BC.ActualWidth;
                    }
                    if (offset == -1)
                    {
                        //shift left
                        P_Rimage.Source = new BitmapImage(new Uri(Urls[Current - 1]));
                        P_BC.SetValue(, 2);
                        P_BR.SetValue(, 0);
                         = P_BC.ActualWidth;
                         = -P_BC.ActualWidth;
                         = 0;
                    }
                    (this, L_Translate, true);
                    break;
            }
        }
    }
}