基于CefSharp开发浏览器(四)浏览器文件下载

一、CefSharp文件下载分析

查看ChromiumWebBrowser类发现cef数据下载处理在IDownloadHandler中进行,但并未找到相应的实现类,故我们需要自己实现DownloadHandler

创建CustomDownloadHandler类并实现IDownloadHandler接口

public class CustomDownloadHandler : IDownloadHandler
{
    public void OnBeforeDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem,
        IBeforeDownloadCallback callback)
    {
        throw new System.NotImplementedException();
    }

    public void OnDownloadUpdated(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem,
        IDownloadItemCallback callback)
    {
        throw new System.NotImplementedException();
    }
}

IDownloadHandler中声明了两个方法,从方法命名来看 OnBeforeDownload和OnDownloadUpdated一个是在下载之前处理,一个在下载中处理

1、首先看第一个方法OnBeforeDownload

DownloadItem 类如下图这里给我们提供了下载文件的相关信息(文件建议名称、源地址、文件大小、当前大小等)

基于CefSharp开发浏览器(四)浏览器文件下载

查看IBeforeDownloadCallback接口定义 

IsDisposed 判断当前下载回调对象是否已释放

Continue 继续下载 第一个参数为下载路径、第二个数是否弹出保存对话框

基于CefSharp开发浏览器(四)浏览器文件下载

2、接下来我们看 OnDownloadUpdated

方法中 DownloadItem 与 OnBeforeDownload中相同

再看回调接口 IDownloadItemCallback ,提供了取消下载、暂停下载、继续下载等方法

基于CefSharp开发浏览器(四)浏览器文件下载

 3、综上所述

CefSharp下载前的设置可以在 OnBeforeDownload中进行处理

下载的实时状态可以在 OnDownloadUpdated进行处理

二、通知外部正在下载文件

当文件下载时我们需要通知浏览器显示下载工具栏并更新下载状态信息

在 CustomDownloadHandler中新增_downloadCallBackEvent事件

private readonly Action<bool, DownloadItem> _downloadCallBackEvent;//第一个参数为true为update

第一个参数判断在OnBeforeDownload还是在OnDownloadUpdated中执行

public void OnBeforeDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem,
           IBeforeDownloadCallback callback)
{
    if (callback.IsDisposed) return;
    _downloadCallBackEvent?.Invoke(false, downloadItem);
    downloadItem.IsInProgress = true;
    var path = GetDownloadFullPath(downloadItem.SuggestedFileName);
    callback.Continue(path, false);
}


public void OnDownloadUpdated(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem,
    IDownloadItemCallback callback)
{
    _downloadCallBackEvent?.Invoke(true, downloadItem);
}

三、设计下载任务栏

基于CefSharp开发浏览器(四)浏览器文件下载

上图为Edge下载任务栏

据图可以分为三个部分,红框部分存在一个或多个,两个绿框部分为固定部分

创建 UserControl 【DownloadToolUc】 暂根据上图设计UI如下

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <StackPanel Grid.Column="0" x:Name="ItemsParent" Orientation="Horizontal" HorizontalAlignment="Left">

    </StackPanel>
    <Button Grid.Column="1" Style="{DynamicResource Button.DownloadLookAllButton}" Content="全部显示" Click="ShowAll_OnClick"/>
    <Button Grid.Column="2" Style="{DynamicResource Button.DownloadCloseButton}" Margin="5,0" Click="CloseDownloadTool_OnClick"/>
</Grid>

红框部分单独创建UserControl 【DownloadToolItemUc】

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <Image Grid.Column="0" Margin="10,0" Source="{DynamicResource DrawingImage.File}" Width="25" Height="25"/>
    <StackPanel Grid.Column="1" HorizontalAlignment="Left">
        <TextBlock Text="{Binding FileName}"  FontSize="14" Margin="0,5,0,0" TextTrimming="CharacterEllipsis"/>
        <Grid>
            <TextBlock Margin="0,5,0,0" Visibility="Collapsed">
                <Hyperlink FontSize="14" Foreground="{DynamicResource WebBrowserBrushes.OpenFileForeground}" Focusable="False" Click="OpenFile_OnClick">打开文件</Hyperlink>
            </TextBlock>
            <StackPanel Margin="0,5,0,0">
                <ProgressBar Height="6"/>
                <TextBlock FontSize="12" Margin="0,5,0,0" TextTrimming="CharacterEllipsis">
                    <Run Text="{Binding CurrentSizeStr}"/>
                    <Run Text="/"/>
                    <Run Text="{Binding TotalSizeStr}"/>
                </TextBlock>
            </StackPanel>
        </Grid>
    </StackPanel>
    <Button Grid.Column="2" Style="{DynamicResource Button.DownloadLookAllButton}" Width="35" Height="35" Content=". . ." Padding="0,0,0,9" Margin="10,0,5,0"/>
</Grid>

四、下载绑定处理

1、为DownloadToolItemUc添加ViewModel

 public class DownloadToolItemViewModel : BaseViewModel
    {
        private string _currentSizeStr;
        public string CurrentSizeStr { get => _currentSizeStr; set { _currentSizeStr = value; OnPropertyChanged("CurrentSizeStr"); } }

        private string _totalSizeStr;
        public string TotalSizeStr { get => _totalSizeStr; set { _totalSizeStr = value; OnPropertyChanged("TotalSizeStr"); } }

        private string _fileName;
        public string FileName { get => _fileName; set { _fileName = value; OnPropertyChanged("FileName"); } }

        public string ConvertFileSize(long size)
        {
            if (size > 1024 * 1024 * 1024)
            {
                return $"{size / (1024 * 1024 * 1024)}G";
            }
            if (size > 1024 * 1024)
            {
                return $"{size / (1024 * 1024)}M";
            }
            return size > 1024 ? $"{size / 1024}K" : $"{size}B";
        }
    }

2、在DownloadToolUc中增加 DownloadFile方法

public void DownloadFile(bool isUpdate, DownloadItem downloadItem)
{
    if (!isUpdate)
    {
        if (_downloadDict.ContainsKey(downloadItem.Id)) return;
        var viewModel = new DownloadToolItemViewModel
        {
            FileName = downloadItem.SuggestedFileName,
        };
        _downloadDict.Add(downloadItem.Id, viewModel);

        this.Dispatcher.Invoke(new Action(() =>
        {
            this.Visibility = Visibility.Visible;
            var item = new DownloadToolItemUc { DataContext = viewModel };
            ItemsParent.Children.Insert(0, item);
        }));

    }
    else
    {
        if (!_downloadDict.ContainsKey(downloadItem.Id)) return;
        var item = _downloadDict[downloadItem.Id];
        item.CurrentSizeStr = item.ConvertFileSize(downloadItem.ReceivedBytes);
        item.TotalSizeStr = downloadItem.TotalBytes <= 0 ? "未知" : item.ConvertFileSize(downloadItem.TotalBytes);
    }
}

3、在创建新TabItem时建立回调事件与DownloadFile绑定

var uc = new WebTabItemUc { ViewModel = { CurrentUrl = obj?.ToString() } };
uc.CefWebBrowser.DownloadCallBackEvent += DownloadTool.DownloadFile;

基于CefSharp开发浏览器(四)浏览器文件下载

五、下载进度绑定

在DownloadToolItemViewModel 中增加如下两个属性

private double _currentSize;
public double CurrentSize { get => _currentSize; set { _currentSize = value; OnPropertyChanged("CurrentSize"); } }

private double _totalSize;
public double TotalSize { get => _totalSize; set { _totalSize = value; OnPropertyChanged("TotalSize"); } }

在DownloadToolItemUc控件中绑定

<ProgressBar Height="6" Maximum="{Binding TotalSize}" Value="{Binding CurrentSize}"/>

注意在给ViewModel赋值时downloadItem.TotalBytes可能为0,故增加TotalSize增加如下处理

item.TotalSize = downloadItem.TotalBytes > downloadItem.ReceivedBytes? downloadItem.TotalBytes : downloadItem.ReceivedBytes;
item.CurrentSize = downloadItem.ReceivedBytes;

基于CefSharp开发浏览器(四)浏览器文件下载

六、下载栏增加显隐动画

任务栏的显示和隐藏有些突兀,为了显示丝滑一些,为其增加简单关键帧动画

为 DownloadToolUc增加 RenderTransform特效 并定义Storyboard资源如下

    <UserControl.RenderTransform>
        <TransformGroup>
            <ScaleTransform/>
            <SkewTransform/>
            <RotateTransform/>
            <TranslateTransform Y="50"/>
        </TransformGroup>
    </UserControl.RenderTransform>
    <Grid Background="{DynamicResource WebBrowserBrushes.DownloadToolBackground}" x:Name="ParentGrid">
        <Grid.Resources>
            <!-- 显示下载工具栏 -->
            <Storyboard x:Key="DisplayTool" RepeatBehavior="1x">
                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)" Storyboard.TargetName="DownloadUc">
                    <EasingDoubleKeyFrame Value="0" KeyTime="00:00:01">
                        <EasingDoubleKeyFrame.EasingFunction>
                            <QuarticEase EasingMode="EaseInOut"/>
                        </EasingDoubleKeyFrame.EasingFunction>
                    </EasingDoubleKeyFrame>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>

            <Storyboard x:Key="HideTool" RepeatBehavior="1x">
                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)" Storyboard.TargetName="DownloadUc">
                    <EasingDoubleKeyFrame Value="50" KeyTime="00:00:01">
                        <EasingDoubleKeyFrame.EasingFunction>
                            <QuarticEase EasingMode="EaseInOut"/>
                        </EasingDoubleKeyFrame.EasingFunction>
                    </EasingDoubleKeyFrame>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
        </Grid.Resources>

此处我们使用TranslateTransform(平移动画) <TranslateTransform Y="50"/> 意味着初始时距离0,0点向下50

显示下载工具栏时使纵坐标回到0点,隐藏时使纵坐标到达50

在cs文件中增加动画启动方法

private void InitStoryboard()
{
    _displayToolStoryboard = (Storyboard)ParentGrid.FindResource("DisplayTool");
    _hideToolStoryboard = (Storyboard)ParentGrid.FindResource("HideTool");
}

private void DisplayTool()
{
    _displayToolStoryboard.Begin();
}

private void HideTool()
{
    _hideToolStoryboard.Begin();
}

在下载时执行DisplayTool 在关闭时执行HideTool

基于CefSharp开发浏览器(四)浏览器文件下载

七、源码地址

gitee地址:https://gitee.com/sirius_machao/mweb-browser