2021-09-19

WPF实现带滚动字幕的浮窗

1、形式

做这个东西时我只是单纯觉得有趣,而且也能进行近期工作的提醒,做完以后很长时间没有改动,现在看来还是有许多不足的。先看一下窗口的具体样式在这里插入图片描述
文本设计的具体文档如下:

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp2"
        mc:Ignorable="d"
        Title="MainWindow" Height="184" Width="829" WindowStartupLocation="Manual" WindowStyle="None" BorderBrush="White" MouseDown="Window_MouseDown" MouseMove="Window_MouseMove" AllowsTransparency="True" Loaded="Window_Loaded" HorizontalAlignment="Center" VerticalAlignment="Top" Top="-3" Left="700">
    <Window.Background>
        <SolidColorBrush Color="White" Opacity="0.5"/>
    </Window.Background>
    <Grid MouseDown="Grid_MouseDown" MouseMove="Grid_MouseMove" Opacity="0.5">
        <Grid.RowDefinitions>
            <RowDefinition Height="19*"/>
            <RowDefinition Height="131*"/>
            <RowDefinition Height="34*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="114*"/>
            <ColumnDefinition Width="611*"/>
            <ColumnDefinition Width="104*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column="3" Orientation="Horizontal" Margin="10,0,-2,0">
            <Button Content="" Width="19" Click="Button_Click_3">
                <Button.Background>
                    <ImageBrush ImageSource="zuixiaoh.png"/>
                </Button.Background>
            </Button>
            <Button x:Name="Button_stop" Content="" Width="19" Click="Button_stop_Click">
                <Button.Background>
                    <ImageBrush ImageSource="renew.png"/>
                </Button.Background>
            </Button>
            <Button Content="&#xA;" Width="19" Click="Button_Click_2">
                <Button.Background>
                    <ImageBrush ImageSource="STOp.png">
                        <ImageBrush.RelativeTransform>
                            <TransformGroup>
                                <ScaleTransform CenterY="0.5" CenterX="0.5" ScaleX="0.9" ScaleY="0.9"/>
                                <SkewTransform CenterY="0.5" CenterX="0.5"/>
                                <RotateTransform CenterY="0.5" CenterX="0.5"/>
                                <TranslateTransform/>
                            </TransformGroup>
                        </ImageBrush.RelativeTransform>
                    </ImageBrush>
                </Button.Background>
            </Button>
            <Button Content="" HorizontalAlignment="Right" Width="19" Click="Button_Click_1" Foreground="{x:Null}" Height="19" VerticalAlignment="Bottom">
                <Button.Background>
                    <ImageBrush ImageSource="2.png">
                        <ImageBrush.RelativeTransform>
                            <TransformGroup>
                                <ScaleTransform CenterY="0.5" CenterX="0.5" ScaleX="1.1" ScaleY="1.05"/>
                                <SkewTransform CenterY="0.5" CenterX="0.5"/>
                                <RotateTransform CenterY="0.5" CenterX="0.5"/>
                                <TranslateTransform/>
                            </TransformGroup>
                        </ImageBrush.RelativeTransform>
                    </ImageBrush>
                </Button.Background>
            </Button>
            <Button Content="" Click="Button_Click" Width="18">
                <Button.Background>
                    <ImageBrush ImageSource="3.png"/>
                </Button.Background>
            </Button>
        </StackPanel>
        <TextBlock x:Name="TB_Content" Grid.Column="2" Grid.Row="1" TextWrapping="Wrap" Background="#FF04F71A" FontSize="24"/>
        <TextBlock x:Name="TB_Content1" Grid.Column="2" Grid.Row="2" TextWrapping="Wrap"/>
        <Image Grid.ColumnSpan="2" Grid.Row="1" Margin="0,0,0,20" Source="11.png"/>

    </Grid>
</Window>


在窗体属性 外观项中设置WindowStyle为None,然后根据需要设置各个按键即可。

2、具体实现

(1)窗体移动
注意一点,当设置WindowStyle为None时,用鼠标是不能移动窗体的,为了实现窗体移动,需要定义鼠标事件Window_MouseDown和Window_MouseMove,注意不要写Grid_MouseDown和Grid_MouseMove
首先定义鼠标与窗体左上角的距离`

 private void Mouse_Procedure(Point Mouse_Postion)
        {
            this.Left += Mouse_Postion.X - FormerX;
            this.Top += Mouse_Postion.Y - FormerY;
        }

之后定义Grid_MouseDown和Grid_MouseMove事件

private void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            FormerX = e.GetPosition(this).X;
            FormerY = e.GetPosition(this).Y;
        }

        private void Window_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                Mouse_Procedure(Mouse.GetPosition(this));
            }
            if (this.Top <= 0)
                this.Top = 0;
        }

用鼠标按下时距窗口的距离初始化FormerX和FormerY,并在移动过程中保持FormerX和FormerY不变。
同时可以用MouseUp事件来使窗口自动隐藏,实现比较简单,就不贴代码了。

(2)文字输出
这是重头戏,在这里我采用了task来保证窗口响应
主体代码如下:

public MainWindow()
        {
            InitializeComponent();
            if (File.Exists("Settings.txt"))
            {
               // SetBinding("Settings.txt");
                // FileShow("Slogan.txt");
                this.Top = 0;
            }
            else
                MessageBox.Show("Check the completion");


            if (File.Exists("Slogan.txt")) 
            {
                FileShow("Slogan.txt");
                this.Top = 0;
            }
           TimeShow();
        }

为了实现滚动字体,定义了FileShow函数,用于读取文件并显示,在其中异步读取文件流,并通过Move函数实现滚动字幕

async private void FileShow(string FilePath)
        {
            Stream StringStream = new FileStream(FilePath, FileMode.Open);
            StreamReader TextRead = new StreamReader(StringStream, Encoding.Default);
            string[] Target = (await TextRead.ReadToEndAsync()).Split('\r');
            await Move(Target);
            TextRead.Dispose();
            StringStream.Dispose();
        }

move函数定义如下:

async Task Move(string[] Target)
        {
            int i = 0;
            while (status)
            {
                TB_Content.Text = "";
                TB_Move(Target[i].Trim(new char[] { '\n', ' ' }));
                await Task.Delay(Target[i].Length * 100 + 1000);
                if (i < Target.Count() - 1)
                    i++;
                else
                    i = 0;
            }
            // return Task.CompletedTask;
            return;
        }

在其中status为全局变量,用于控制该代码块的停止,利用 TB_Move来控制滚动字幕,i为控制循环播放的变量,await Task.Delay(Target[i].Length * 100 + 1000);是必要的,否则就会出现前后字幕冲突,1000为两段字幕之间的间隔时间

TB_Move定义如下

  async private void TB_Move(string ShowString)
        {
            for (int i = 0; i < ShowString.Length; i++)
            {
                await Task.Delay(100);
                if (i % count != 0)
                    TB_Content.Text += ShowString[i];
                else if (i % count == 0 && i != 0)
                    TB_Content.Text += "\r\n" + ShowString[i];
                else
                    TB_Content.Text += ShowString[i];
            }

        }

await Task.Delay(100);中的100为一个字出现的延时时间,现在定为0.1s,可以自行修改。

注意一点,我在这里把字幕文字文件设为"Slogan.txt",文件中用回车符作为两段之间的间隔符

此外还加入了倒计时的功能,具体以 TimeShow函数实现:

async private void TimeShow()
        {
            Stream StringStream = new FileStream("countdown.txt", FileMode.Open);
            StreamReader TextRead = new StreamReader(StringStream, Encoding.Default);
            string Title= await TextRead.ReadLineAsync();
            DateTime Enddate =DateTime.Parse((await TextRead.ReadLineAsync()));
            await TimeExcuete(Title,Enddate);
            TextRead.Dispose();
            StringStream.Dispose();
        }

TimeExcuete进行控制:

 async Task  TimeExcuete(string Title,DateTime EndDate)
        {
            while (status)
            {
              TB_Content1.Text = Title+":" + (EndDate - DateTime.Now).ToString(@"dddd\-hh\:mm\:ss\.ff");
                await Task.Delay(10);
            }
            return;
            }

文件存储于"countdown.txt",第一行为标题,第二行为截止日期

附加功能

(1)关闭、最小化窗体,很简单,不做说明
(2)停止与刷新:
停止:

private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            status = !status;
        }

刷新:

  private void Button_stop_Click(object sender, RoutedEventArgs e)
    {
        status = true;
        FileShow("Slogan.txt");
        TimeShow();
    }

如果想要在之前的位置进行读取,将Move函数里的i改为全局变量即可。

附录

现在把完整代码附在最后,如果要把窗口设置为总在最前,将公共中的topMost属性勾选即可
这个代码是不含自动隐藏和显示的,如果要实现功能还需进一步开发。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;

namespace WpfApp2
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {

        private double FormerX = 0;
        private double FormerY = 0;

        bool status = true;
        int count = 0;
        public MainWindow()
        {
            InitializeComponent();
            if (File.Exists("Settings.txt"))
            {
               // SetBinding("Settings.txt");
                // FileShow("Slogan.txt");
                this.Top = 0;
            }
            else
                MessageBox.Show("Check the completion");


            if (File.Exists("Slogan.txt")) 
            {
                FileShow("Slogan.txt");
                this.Top = 0;
            }
           TimeShow();
        }

      async private void SetBinding(string FilePath)
        {
            Stream StringStream = new FileStream(FilePath, FileMode.Open);
            StreamReader TextRead = new StreamReader(StringStream, Encoding.Default);
            
        }

        async private void FileShow(string FilePath)
        {
            Stream StringStream = new FileStream(FilePath, FileMode.Open);
            StreamReader TextRead = new StreamReader(StringStream, Encoding.Default);
            string[] Target = (await TextRead.ReadToEndAsync()).Split('\r');
            await Move(Target);
            TextRead.Dispose();
            StringStream.Dispose();
        }


        async Task Move(string[] Target)
        {
            int i = 0;
            while (status)
            {
                TB_Content.Text = "";
                TB_Move(Target[i].Trim(new char[] { '\n', ' ' }));
                await Task.Delay(Target[i].Length * 100 + 1000);
                if (i < Target.Count() - 1)
                    i++;
                else
                    i = 0;
            }
            // return Task.CompletedTask;
            return;
        }

        async private void TimeShow()
        {
            Stream StringStream = new FileStream("countdown.txt", FileMode.Open);
            StreamReader TextRead = new StreamReader(StringStream, Encoding.Default);
            string Title= await TextRead.ReadLineAsync();
            DateTime Enddate =DateTime.Parse((await TextRead.ReadLineAsync()));
            await TimeExcuete(Title,Enddate);
            TextRead.Dispose();
            StringStream.Dispose();
        }

        async Task  TimeExcuete(string Title,DateTime EndDate)
        {
            while (status)
            {
              TB_Content1.Text = Title+":" + (EndDate - DateTime.Now).ToString(@"dddd\-hh\:mm\:ss\.ff");
                await Task.Delay(10);
            }
            return;
            }

      

            async private void TB_Move(string ShowString)
        {
            for (int i = 0; i < ShowString.Length; i++)
            {
                await Task.Delay(100);
                if (i % count != 0)
                    TB_Content.Text += ShowString[i];
                else if (i % count == 0 && i != 0)
                    TB_Content.Text += "\r\n" + ShowString[i];
                else
                    TB_Content.Text += ShowString[i];
            }

        }

        

        private void Mouse_Procedure(Point Mouse_Postion)
        {

            this.Left += Mouse_Postion.X - FormerX;
            this.Top += Mouse_Postion.Y - FormerY;

        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
        {
           
        }

        private void Grid_MouseMove(object sender, MouseEventArgs e)
        {
           
        }

        private void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            FormerX = e.GetPosition(this).X;
            FormerY = e.GetPosition(this).Y;
        }

        private void Window_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                Mouse_Procedure(Mouse.GetPosition(this));
            }
            if (this.Top <= 0)
                this.Top = 0;
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Settings");
           
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            count = (int)(TB_Content.ActualWidth / TB_Content.FontSize);
        }

        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            status = !status;
        }

        private void Button_stop_Click(object sender, RoutedEventArgs e)
        {
            status = true;
            FileShow("Slogan.txt");
            TimeShow();
        }

        private void Button_Click_3(object sender, RoutedEventArgs e)
        {
            this.WindowState = WindowState.Minimized;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值