首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ProgressBar.Value在FsXaml和ElmishWPF中的更新

ProgressBar.Value在FsXaml和ElmishWPF中的更新
EN

Stack Overflow用户
提问于 2021-12-03 01:15:45
回答 1查看 234关注 0票数 3

我正在尝试更新ProgressBar.Value中的FsXaml。在C#中,我使用了以下代码.我没有尝试在F#中实现F#方法,因为在我看来,使用公共字段(myCaller)并不是一种功能方法(更不用说我不知道在F#中使用这种C#方法是否可能)。

代码语言:javascript
复制
//C# code
namespace Special_technical_dictionary_CSharp_4._011
    {
    //...some usings
    class ExcelData
        {
         //...some code    
        public void WritingIntoDat()
            {            
            //...some code
            using (bw = new BinaryWriter(new FileStream(...some params...)))
                {
                while ((currrowIndex < (lastrowIndex + 1)))
                    {
                    //...some code                   
                    Form1.myCaller.updateProgressBarValue(100 * currrowIndex);
                    currrowIndex += 1;
                    }
                bw.Close();
                }
            //...some code 
            }
        }
    }

namespace Special_technical_dictionary_CSharp_4._011
    {
    //...some usings
    public partial class Form1 : Form
        {
        //...some code
        public static Form1 myCaller;
        
        public Form1()
            {
            InitializeComponent();
            myCaller = this;
            }
        //...some code
        public void updateProgressBarValue(int valueV)           
            => progressBar.Value = (progressBar.Value == progressBar.Maximum) ? valueV : 0;
        //...some code
        }
    }

我的问题是:在更新ProgressBar.Value的F# (FsXaml/代码后面)中,什么是最好的(或者至少是好的)功能方法?

EDIT1:

删除不相关的代码和文本。那些对Elmish.WPF不感兴趣的人,请等到FsXaml的答案出现。

EDIT2:

Elmish.WPF

我试图使用本特兰伯格的评论和答案以及他的优秀示例代码来处理ProgressBar问题。我的适应性适用于for -循环,但不适用于List.map(i)/iter(i),这是我实际上需要进度条的集合函数。以下是简化的代码:

文件: MainWindow.fs

代码语言:javascript
复制
//F# code
module Elmish.MainWindow

type ProgressIndicator = Idle | InProgress of percent: int

type Model =
    {                     
        ProgressIndicatorLeft: ProgressIndicator
        ProgressIndicatorRight: ProgressIndicator
    }

let initialModel = 
    {
        ProgressIndicatorLeft = Idle 
        ProgressIndicatorRight = Idle         
    }

let init() = initialModel, Cmd.none
    
type Msg =   
    | UpdateStatusLeft of progress: int
    | WorkIsCompleteLeft 
    | UpdateStatusRight of progress: int
    | WorkIsCompleteRight 
    | TestButtonLeftEvent
    | TestButtonRightEvent    

 // FOR TESTING PURPOSES ONLY
let private longRunningOperationLeft dispatch = //simulating long running operation
    async
        {
            for i in 1..100 do 
                do! Async.Sleep 20
                dispatch (UpdateStatusLeft i) //THIS WORKS
            dispatch WorkIsCompleteLeft
        }  
    
 // FOR TESTING PURPOSES ONLY
let private longRunningOperationRight dispatch = //simulating long running operation    
    async  //NOT WORKING
        {
            [1..10000]    
            |> List.mapi(fun i item -> 
                                     [1..100] |> List.reduce (*) |> ignore 
                                     dispatch(UpdateStatusRight i)   
                         ) 
            dispatch WorkIsCompleteRight
        }   

let update (msg: Msg) (m: Model) : Model * Cmd<Msg> = 
    match msg with 
        | UpdateStatusLeft progress  -> { m with ProgressIndicatorLeft = InProgress progress; ProgressBackgroundLeft = Brushes.White }, Cmd.none                      
        | WorkIsCompleteLeft         -> { m with ProgressIndicatorLeft = Idle; ProgressBackgroundLeft = Brushes.LightSkyBlue }, Cmd.none                       
        | UpdateStatusRight progress -> { m with ProgressIndicatorRight = InProgress progress; ProgressBackgroundRight = Brushes.White }, Cmd.none                       
        | WorkIsCompleteRight        -> { m with ProgressIndicatorRight = Idle; ProgressBackgroundRight = Brushes.LightSkyBlue }, Cmd.none 
        | TestButtonLeftEvent        ->
                                      let incrementDelayedCmd (dispatch: Msg -> unit) : unit =  //THIS WORKS
                                          let delayedDispatch = longRunningOperationLeft dispatch                                                      
                                          Async.StartImmediate delayedDispatch
                                      { m with ProgressIndicatorLeft = InProgress 0 }, Cmd.ofSub incrementDelayedCmd           
        | TestButtonRightEvent       ->
                                      let incrementDelayedCmd (dispatch: Msg -> unit) : unit =  //NOT WORKING              
                                          let delayedDispatch = longRunningOperationRight dispatch
                                          Async.StartImmediate delayedDispatch
                                      { m with ProgressIndicatorRight = InProgress 0 }, Cmd.ofSub incrementDelayedCmd   
 
let bindings(): Binding<Model,Msg> list =
   [      
      "ProgressLeftBackg"    |> Binding.oneWay(fun m -> m.ProgressBackgroundLeft) 
      "ProgressRightBackg"   |> Binding.oneWay(fun m -> m.ProgressBackgroundRight) 
      "ProgressLeft"         |> Binding.oneWay(fun m -> match m.ProgressIndicatorLeft with Idle -> 0.0 | InProgress v -> float v)
      "ProgressRight"        |> Binding.oneWay(fun m -> match m.ProgressIndicatorRight with Idle -> 0.0 | InProgress v -> float v)       
      "TestButtonLeft"       |> Binding.cmdIf(TestButtonLeftEvent, fun m -> match m.ProgressIndicatorLeft with Idle -> true | _ -> false)
      "TestButtonRight"      |> Binding.cmdIf(TestButtonRightEvent, fun m -> match m.ProgressIndicatorRight with Idle -> true | _ -> false) 
   ]

即使将"i“索引与进度条值绑定对于MainWindow中的集合函数也是有效的,它也不会解决这个问题。在现实生活中,用于处理进度条值的集合函数位于“以上”主窗口文件中。如下所示:

文件: MainLogicRight.fs

代码语言:javascript
复制
//F# code
module MainLogicRight

let textBoxString3 low high path = 

    //some code

    let myArray() =            
        Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)
        |> Option.ofObj
        |> optionToArraySort "..." "..."           
        |> Array.collect
                (fun item -> 
                            let arr = 
                                let p = prefix + "*"
                                Directory.EnumerateDirectories(item, p) 
                                |> Option.ofObj
                                |> optionToArraySort "..." "..."
                                |> Array.Parallel.mapi(fun i item -> 
                                                                let arr = Directory.EnumerateFiles(item, "*.jpg", SearchOption.TopDirectoryOnly)
                                                                          |> Option.ofObj   
                                                                          |> optionToArraySort "..." "..."                                                  
                                                                arr.Length
                                                      ) 
                            arr                    
                )    

我了解到,(可能)不可能将pb值绑定到非索引函数(如Array.collect )。但重要的是,如何将pb值与List/Array.mapi/iteri (Array.Parallel.mapi 中的"i“索引绑定(在本例中) ?

EDIT3:

根据本特的最后一个答复,我删除了现在不相关的文本和评论。基于答案的一个例子是这里

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-12-04 21:43:52

这个答案解释了如何在Elmish.WPF中从异步完成对用户界面的进度更新。

我已经创建了一个GitHub上的实例来演示这个。该示例还演示了调用异步函数和接收结果的另一种方法。它还演示了如何使用mkProgram而不是mkSimple。演示可以用作Elmish.WPF应用程序的起始模板。

演示中的这个片段显示了从异步更新用户界面所涉及的基本代码。

这两种技术都基于埃尔米什书的代码。在Elmish.WPF中,您会发现很多代码也很有用。

我没有尝试在这里更新一个进度条,只是一个状态栏文本框,但是从这里你可以很容易地知道该做什么来更新任何东西。

代码语言:javascript
复制
| UpdateStatusText statusText ->
    { m with StatusText = statusText }, Cmd.none
| RunWithProgress ->
    let incrementDelayedCmd (dispatch: Msg -> unit) : unit =
        let delayedDispatch = async {
            do! Async.Sleep 1000
            dispatch (UpdateStatusText "One")
            do! Async.Sleep 1000
            dispatch (UpdateStatusText "Two")
            do! Async.Sleep 1000
            dispatch (UpdateStatusText "Three")
            }
        Async.StartImmediate delayedDispatch
    { m with StatusText = "Started progress." }, Cmd.ofSub incrementDelayedCmd

更新:

我现在已经更新了GitHub上的演示项目,以便从异步中演示进度条(和状态文本)的更新。这些都是重要片段的片段。

从异步发送的两个消息的声明。

代码语言:javascript
复制
| UpdateStatus of statusText:string * progress:int
| WorkIsComplete // This message could carry a result from the work done.

处理这两条消息。

代码语言:javascript
复制
| UpdateStatus (statusText, progress) ->
    { m with StatusText = statusText; Progress = progress }, Cmd.none
| WorkIsComplete ->
    { m with StatusText = "Work was completed."; Progress = 0 }, Cmd.none
| RunWithProgress ->
    let incrementDelayedCmd (dispatch: Msg -> unit) : unit =
        let delayedDispatch = async {
            do! Async.Sleep 1000
            dispatch (UpdateStatus ("Early work", 30))
            do! Async.Sleep 1000
            dispatch (UpdateStatus ("Still working", 60))
            do! Async.Sleep 1000
            dispatch (UpdateStatus ("Late work", 90))
            do! Async.Sleep 1000
            dispatch WorkIsComplete
            }
        Async.StartImmediate delayedDispatch
    { m with StatusText = "Started progress." }, Cmd.ofSub incrementDelayedCmd

字段进度声明为int。

代码语言:javascript
复制
    Progress: int

ProgressBar的属性值是浮点数,因此绑定中需要对浮点数进行强制转换。

代码语言:javascript
复制
"Progress" |> Binding.oneWay (fun m -> float m.Progress)

当然,我们可以将模型中的进步声明为浮动,但我想借此机会指出,模型不必与组件属性的数据类型保持一致。当然,我们可以在绑定中以我们想要的任何方式绘制地图。

调度员的最后一个注意事项。这可以通过Cmd.ofSub访问,也可以通过WkProgram.Subscribe访问。在另一种情况下,可能会有更多的信息,但是现在请注意:使用dispatcher发送消息是线程安全的。这意味着您可以向模型发送进度消息(或任何消息),也可以从在顶级异步函数中运行的异步函数发送进度消息(或任何消息),也可以通过计时器事件或任何真正的地方发送。

最后更新: GitHub上的演示现在比这里显示的稍微先进一些,但是原理仍然相同,所以我不会费心在这个答案中更新源代码。任何对此感兴趣的人都很可能需要完整的演示源代码,除非您已经熟悉了Elmish.WPF

问题的最后一部分(稍后添加)在这里得到了回答.

当做冗长和/或CPU密集型的工作时,这应该按照下面的longRunningOperationLeft函数所示进行。这也显示了其他不依赖于GUI的函数是如何编写的,这样就可以将进度更新发送到GUI。

下面所示的longRunningOperationRight是错误的,阻塞了GUI。

我在异步和任务方面的专门知识不是很好,但我认为从Elmish调用的顶级异步函数(如longRunningOperationLeft)运行在与Elmish循环相同的线程上,这就是为什么不应该用任何冗长或CPU密集型的东西来阻止它们。相反,这种阻塞工作需要进入子计算(如workToDo)。longRunningOperationLeft的作用是等待工作,而不是自己工作,以免阻塞GUI。

我不知道List.mapi内部是否可以有异步操作。我怀疑不是。不管怎么说,我想这对你的现实生活来说是不必要的。

米拉的更新:你是对的。在我的现实生活中不需要。在List/array.mapi 中添加 reportProgress i (如在代码中)就足够了。

代码语言:javascript
复制
let private lengthyWork () =
    [1..20_000_000] |> List.reduce ( * ) |> ignore

let private workToDo reportProgress = async {
    reportProgress 0
    lengthyWork ()
    reportProgress 25
    lengthyWork ()
    reportProgress 50
    lengthyWork ()
    reportProgress 75
    lengthyWork ()
    reportProgress 100
    return 7
    }

// This is good.
let private longRunningOperationLeft dispatch = async {
    let reportProgress progress = dispatch (UpdateStatusLeft progress)
    let! hardWork = Async.StartChild (workToDo reportProgress)
    do! Async.Sleep 1000 // Can do some async work here too, while waiting for hardWork to finish.
    let! result = hardWork
    dispatch WorkIsCompleteLeft
    }

// This is not good. Blocking GUI.
let private longRunningOperationRight dispatch = async {
    dispatch (UpdateStatusRight 0)
    lengthyWork ()
    dispatch (UpdateStatusRight 25)
    lengthyWork ()
    dispatch (UpdateStatusRight 50)
    lengthyWork ()
    dispatch (UpdateStatusRight 75)
    lengthyWork ()
    dispatch (UpdateStatusRight 100)
    dispatch WorkIsCompleteRight
    }
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70208381

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档