0条留言

00_Title

这篇博客文章是 F#日历2020。非常感谢 谢尔盖 在今年组织起来的时候,一定要在阅读这篇博客之后阅读其他博客帖子。

极好框架基于模型视图更新(MVU)模式,提供了编写功能性第一移动(和桌面)客户端的方法。如果您不熟悉Fabulous,建议您检查一下 发布 通过 提姆 (Fabulous的维护者)或官员 docs.

后端实现为已启用SignalR的ASP.NET Core Web应用程序。你可以看看如何 这里.

为了展示如何将SignalR与Fabulous结合使用,我们将实现bet188地址聊天应用程序。而且我觉得这叫“很棒的聊天”-看看用这个框架做很棒的事情有多么容易?因此,让我们忽略以下问题:“这个世界真的需要另bet188地址聊天应用程序吗?!”在我们的脑海中,让我们看看我们将在此应用程序中实现什么,以突出显示如何在功能齐全的第bet188地址移动应用程序中使用SignalR。

假设我们有以下要求:

  • 用户应通过其姓名来识别自己。
  • 用户应该能够发送消息。
  • 该应用程序处于活动状态时,用户应该能够阅读消息。

换句话说,两个操作完全由UI驱动,用户输入他的用户名,然后进入聊天。用户键入一条消息,然后按一下按钮将其发送。但是,当涉及到能够阅读消息时,该事件将从后台触发。此外,SignalR连接通常建立一次,然后用于会话的其余部分。因此,让我们在应用中创建bet188地址模块,其中将包含与SignalR交互有关的所有操作。

module SignalR =
    let connectToServer =
        // connect to SignalR service

    let startListeningToChatMessages (connection: HubConnection) dispatch =
        // receive messages

    let sendMessage (connection: HubConnection) (message: ChatMessage) =
        // send message

用户提供其姓名后,我们将连接到该服务。不是因为它本身是必需的。但是,如果稍后我们决定添加一些适当的身份验证,则不会改变应用程序的流程。事不宜迟,让我们实现登录视图。

该图显示了登录视图

我们用于此部分的视图如下所述。

let view model dispatch =
    View.ContentPage
        (title= "登录",
         content =
             View.StackLayout
                 (verticalOptions = LayoutOptions.Center,
                  horizontalOptions = LayoutOptions.Center,
                  children =
                      [ View.Label(text = "Please enter your Username")
                        View.Entry
                            (text = model.Username,
                             maxLength = 15,
                             placeholder = "Please enter your username",
                             completed = (fun _ -> (loginUser dispatch)),
                             textChanged = fun e -> dispatch (UsernameChanged e.NewTextValue))
                        View.Button(text = "登录", command = fun _ -> (loginUser dispatch))
                        ]))

一旦用户点击 登录 按钮,我们要建立SignalR连接。

let connectToServer =
    let connection =
        HubConnectionBuilder()
            .WithUrl(Config.SignalRUrl)
            .WithAutomaticReconnect()
            .Build()

    async {
        do! connection.StartAsync() |> Async.AwaitTask
        return connection
    }

Since we will want to hold on to the connection, we will invoke a Command which will be evaluated in the Update method.

let update msg (model: Model) =
    match msg with
    // ... other message handling
    | Connected connection ->
        { model with
              SignalRConnection = Some connection
              AppState = Ready },
        Cmd.none
    // ... more message handling

现在,在用户连接到聊天之后,我们将显示聊天视图。该视图允许用户键入消息,发送消息并阅读连接到该服务的其他用户的响应或问题。

该图显示了实际的聊天视图

让's start with writing messages. Similar as with the username we again have an Entry 和bet188地址 button for sending the messages. Once the user sends a message, we invoke SendAsync in our SignalR module:

let sendMessage (connection: HubConnection) (message: ChatMessage) =
    async {
        let jsonMessage = JsonSerializer.Serialize(message)

        do! connection.SendAsync("SendMessage", jsonMessage)
            |> Async.AwaitTask
    }

So thus far, we have connected to the SignalR service, 和 we can send messages to the server. But we are still missing one essential part, 和 that is how we can receive messages 从 the backend service. What we need to do is register a listener to a specific SignalR-method (called NewMessage). We can implement our function as follows:

let startListeningToChatMessages (connection: HubConnection) dispatch =
    let handleReceivedMessage (msg: string) =
        printfn "Received message: %s" msg
        dispatch (Msg.MessageReceived(JsonSerializer.Deserialize<ChatMessage>(msg)))
        ()

    connection.On<string>("NewMessage", handleReceivedMessage)

现在,在处理程序方法中,每当从SignalR服务收到新消息时,我们将调度bet188地址命令。因此,让我们从一开始就扩展登录功能,不仅可以创建连接,还可以注册接收者。

let loginUser dispatch =
    async {
        let! connection = SignalR.connectToServer
        dispatch (Msg.Connected connection)

        SignalR.startListeningToChatMessages connection dispatch
        |> ignore

        dispatch (Msg.LoggedIn)
    }
    |> Async.StartImmediate

We pass in the dispatcher 从 the view method when registering. This allows us to dispatch a command, i.e. invoke the update method 和bet188地址dd the new message to our list of chat messages:

let update msg (model: Model) =
    match msg with
    // ... other message handling
    | MessageReceived chatMessage ->
        { model with
              Messages = chatMessage :: model.Messages },
        Cmd.none
    // ... more message handling

这样,用户将能够与当前正在使用聊天程序的任何人发送和接收聊天消息。

结论

这就是我们可以在Fabulous应用程序中使用SignalR并创建Fabulous Chat应用程序的方式。也许我们仍然需要仔细研究设计和安全性,才能真正赢得这个声誉。

您还看到了如何处理不是源自用户输入而是在后台发生的事件。每当您使用一些在后台执行其他代码的代码时,都可以使用此技术。

您可以在上找到该应用程序的完整示例 的GitHub.

标题照片作者: 泰勒·拉斯托维奇(Tyler Lastovich)像素

0条留言

TitleImage

不久前,我使用F#类型提供程序创建了bet188地址转换表。那篇文章让我想到了是否有可能编写bet188地址从网站获取数据的应用程序。也许您也收到了有关应用程序的请求。网上没有任何昂贵的东西,实际上应该显示的所有数据都已经存在于网络上,也就是这个网站就在这里。所以问题是:是否可以在不必编写任何后端代码的情况下创建这样的应用程序?

在这篇博客文章中,我将尝试为我最喜欢的Xamarin会议之一创建bet188地址应用程序-the Xamarin专家日。因此,让我们看看是否可以创建我们的神话般的Xamarin Expert Day应用程序。

获取数据

类型提供程序是F#的令人愉快的功能。在编译类型提供程序期间,提供程序会生成数据源中表示的数据模型。我写了 博客文章 在分析HTML的主题之前。这次,我们将使用F#数据随附的另bet188地址工具集 NuGet 包。

由于我们想获取有关Xamarin专家日会议的信息,因此我们可以尝试直接解析该网站。因此,我们可以使用以下行执行此操作:

HtmlDocument.Load "//expertday.forxamarin.com/"

不幸的是,我们生活在JavaScript的现代时代。我不想在这里切线,只是声明bet188地址事实,即Xamarin专家日网站似乎在加载初始HTML之后正在加载有关对话和曲目的信息。幸运的是,当在浏览器中加载页面时,我们将获得bet188地址HTML版本,其中包含了我们正在寻找的所有信息。因此,我们可以直接从文件中加载数据,而不是直接从网站中加载数据。预算项目有其局限性...-

HtmlDocument.Parse("ExpertXamarin.html")

当我们查看网站的HTML(在浏览器中)时,可以看到发言人在以下HTML结构下列出:

<li data-speakerid="df2bc5ca-5a6b-48a9-87ac-71c817d7b240" class="sz-speaker sz-speaker--compact ">
<div class="sz-speaker__photo">
  <a href="#" onclick="return ...');">
    <img src="...894db1.jpg">
  </a>
</div>
<h3 class="sz-speaker__name">
  <a href="#" onclick="return ...');">Mark Allibone</a>
</h3>
<h4 class="sz-speaker__tagline">Lead 移动 Developer Rey Automation, Microsoft MVP</h4>
</li>

因此,我们知道我们可以获取每个发言人的图像,名称,标语和ID。因此,让我们创建一条记录来存储该信息:

type Speaker = {Id:string; Name:string; Photo:string; Tagline:string}


创建记录并非绝对必要。但这确实使以后处理数据变得容易一些。另bet188地址优点是,我们可以将类型提供程序代码封装在.Net标准库中,然后与非F#.Net代码共享。不,您不能直接从C#访问类型提供程序数据类型,而C#中的某些功能受F#启发。据我所知,我不会屏住呼吸,希望很快能看到C#中的类型提供程序...

有了适当的记录后,剩下要做的就是从HTML中提取数据。 F#Data随HTML CSS选择器一起带来的好处。 HTML CSS选择器允许对ID,类和标记类型进行过滤。因此,如果我们想获取发言人的姓名,我们可以过滤组件,然后按如下所示提取值:

// .. other parsing methods
let private getName (htmlNode:HtmlNode) =
    htmlNode.CssSelect("h3.sz-speaker__name > a") |> Seq.map (fun h -> h.DirectInnerText()) |> Seq.head

let getSpeakers (html:string) =
    HtmlDocument.Parse(html)
        .CssSelect("li.sz-speaker")
        |> Seq.map (fun s -> {Id = (getId s); Name = (getName s); Photo = (getPhoto s); Tagline = (getTagline s)})

同样,可以访问其余数据以填充记录中的其他字段。曲目同样如此,我们将再次创建bet188地址记录,在其中存储每个曲目的名称,时间,房间和发言人ID。我们将能够将曲目链接到ID为:

type Track = {Room:string; 提姆e:string; Title:string; SpeakerId:string option}

let getTracks (html:string) =
    HtmlDocument.Parse(html)
        .CssSelect("div.sz-session__card")
        |> Seq.map(fun s -> {Room = (getRoom s); 提姆e = (getTime s); Title = (getTitle s); SpeakerId = (getSpeakerId s) })

现在我们已经拥有了所有数据,是时候开始破解应用程序了。

神话般的Xamarin Experts App

Before we start writing our UI code, there is still that shortcut we took above with loading the information out of a file. While this works great when using a script in the mobile world, this means we have to pack that HTML doc into the app. There are two approaches: either put it into the Assets folder on Android 和 in the Resources folder (you can also use XCAssets...) on iOS or make an Embedded Resource in the .Net Standard library. While the first option would be what Apple 和 Google intended you to use when adding docs, you want to ship with your app. You will have to jump through some hoops to access the document. So let's again save some time 和 just pack the file as an Embedded Resource in our .Net Standard project. Embedded Resources are packed into your apps binary. This results in an awkward fashion of accessing the data. While described in the official docs 这里,这是在Xamarin专家日会议应用程序中实现的方式(我们需要使用更短的名称...):

let loadFile filename =
    let assembly = IntrospectionExtensions.GetTypeInfo(typedefof<Model>).Assembly;
    let stream = assembly.GetManifestResourceStream(filename);
    use streamReader = new StreamReader(stream)
    streamReader.ReadToEnd()

有了这种方式。让我们创建bet188地址包含标题,时间和房间的所有对话的列表。选择曲目时,我们将显示演示文稿的信息以及演讲者信息。

因此,我们可以将清单汇总如下:

let showTrackCell track =
    View.ViewCell( view =
        View.StackLayout(children = [
            View.Label (text = track.Title, 
                        fontSize = FontSize 22.)
            View.Label (text = track.Time + " in " + track.Room, 
                        fontSize = FontSize 14.,
                        fontAttributes = FontAttributes.Italic)
            ]))

let view (model: Model) dispatch =

    View.ContentPage(
        content = match model.SelectedTrack with 
                    | Some track -> showTrackInfo track model dispatch
                    | None -> View.ListView(
                                    rowHeight = 80,
                                    hasUnevenRows = true,
                                    margin = Thickness(8.,0.,0.,0.),
                                    items = (model.Tracks |> List.map showTrackCell),
                                    selectionMode = ListViewSelectionMode.Single,
                                    itemSelected = (fun args -> dispatch (TrackSelected args))
                                    )
        )

而“详细视图”将像这样完成:

let showTrackInfo track (model:Model) dispatch =
    let speaker = match track.SpeakerId with
                  | Some speakerId -> model.Speakers |> Seq.tryPick(fun s -> if s.Id = speakerId then Some s else None)
                  | None -> None

    let addSpeakerInfo (speaker:Speaker) =
        View.StackLayout(margin = Thickness(0.,32.,0.,0.), children = [
                View.Label (text = "Speaker", fontSize = FontSize 22. )
                View.Image (source = (Image.Path speaker.Photo))
                View.Label (text = "Presenter: " + speaker.Name)
                View.Label (text = "Tagline: " + speaker.Tagline)
            ])
        
    let speakerViewElements = match (speaker |> Option.map addSpeakerInfo) with
                              | Some speakerInfo -> speakerInfo
                              | None -> View.Label(text = "Brought to you 通过 the Organizers");

    View.Grid (margin = Thickness(8.,8.,8.,16.),
                rowdefs = [Star; Auto],
                children = [
                    View.StackLayout(children = [
                        View.Label (text = track.Title, fontSize = FontSize 22.)
                        View.Label (text = "In: " + track.Room, fontSize = FontSize 14.)
                        View.Label (text = "At: " + track.Time, fontSize = FontSize 14., margin = Thickness(0.,-4.,0.,0.))
                        speakerViewElements
                        ])
                    (View.Button (text = "Back", command = (fun () -> dispatch (TrackSelected None)))).Row(1)
                ])

您可能已经注意到谈话说明丢失了。该网站具有JavaScript函数,可检索该附加信息。我认为可以将JavaScript调用复制到后端,然后解析答案

JSON / HTML答案。但是,插入莱姆声明为什么我不是懒惰的人。

该应用程序仍然感觉有些虚张声势,我想我可能必须在另一篇博客文章中进行跟进并发布它 漂亮 -

结论

在这个小实验中,我们着手研究是否有可能编写bet188地址移动应用程序,以显示与网站上已经存在的信息相同的信息。虽然路上遇到了一些坎bump,但我在看着JavaScript。确实有可能为Xamarin专家日编写bet188地址可在Android和iOS上运行的应用程序。

虽然我真的应该开始我的下一篇文章,并使该应用程序更漂亮-

您可以在上签出整个应用 的GitHub.

这篇文章是F#出现日历的一部分。一定要看看其他 帖子.

高温超导

0条留言

在阳光明媚的日子里,显示bet188地址笨拙的同龄人和海洋

极好允许使用F编写Xamarin Forms应用程序#以实用的风格神话般的灵感来自榆树,这证明通过使用模型视图更新(以下简称MVU)模式功能语言非常适合编写UI代码。尽管功能代码在空值和竞争条件方面挤占了大量潜在的错误-在本文中,我们将不专注于Fabulous的这一方面。相反,让我们看一下如何创建可以在将来保持可维护状态的精美UI。

此博客文章是Xamarin UI July的一部分,由史蒂文·塞维森。一定要检查我所有的美丽帖子共同作者.

精选#XamarinUIJuly徽章

受到史蒂文斯(Stevens)以前关于-我喜欢称呼-可舔UI的一些帖子的启发。我想说明为什么用代码编写UI可以使您编写不仅美观而且易于维护和扩展的UI。所以对于这篇文章,我将实现我发现的设计思想运球通过味觉.

应用程序设计如小球所示

尽管此博文将重点介绍Fabulous,但在使用C编写应用程序时,您可以应用相同的原理#和XAML。但是您最终将获得一堆文件,并且感觉会更加复杂。 F#一开始要使用的简洁语言,而Fabulous允许使用比C语言通常需要的代码行少的代码编写应用程序#和XAML。我并不是说这就是您退出Fabulous的原因。但这是事实...如果您不熟悉F#接下来是简短介绍。如果这是您所有的老新闻,请随时跳过简介。

极好简介

'从好旧的开始Welcome to Xamarin Forms空白应用:

module App = 
    type Model = 
      { Message : string } // your apps state, we could do without...

    type Msg = 
        | SomeStateChange // just for the demo, we do not need this...

    let initModel = { Message = "Welcome to Xamarin表格!" }

    let init () = initModel, Cmd.none

    let update msg model =
        match msg with
        | SomeStateChange -> model, Cmd.none

    let view (model: Model) dispatch =
        View.ContentPage(
          content = View.StackLayout(
            children = [ 
                View.Label(text = model.Message, horizontalOptions = LayoutOptions.Center, verticalOptions = LayoutOptions.CenterAndExpand)
            ]))

    // Note, this declaration is needed if you enable LiveUpdate
    let program = Program.mkProgram init update view

type App () as app = 
    inherit Application ()

    let runner = 
        App.program
#if DEBUG
        |> Program.withConsoleTrace
#endif
        |> XamarinFormsProgram.run app

是的,这就是您通常在空白C中拥有的所有代码#应用程式。我们不会对所有功能的工作方式进行过多的介绍。在底部,您可以看到type App,翻译成App.xaml.cs类,即任何Xamarin Forms应用程序的入口点。我们的模拟MainPage是个module App。 MVU模式的三个组成部分与Model(bet188地址F#记录,如果您是F新手#认为它是POCO,虽然不完全相同,但目前已经足够接近),并且viewupdate功能。

更新功能是处理视图的所有更改的地方。仅显示文本,此功能实际上没有任何作用。由于稍后我们将重点介绍UI,因此我将向您简要介绍更新功能在一般应用中的作用。想象一下您所有的UI更改和后台任务事件都必须进行依序通过这一点。定义了所有状态更改。您可以重现应用程序的每种状态-哦,没有比赛条件

查看功能包含ContentPage,其中包括StackLayout和bet188地址Label。刚开始,您可能不会考虑太多。但是看看它写得多么简洁。例如,StackLayoutchildren,这是F中的bet188地址简单列表#。因此,将另bet188地址元素添加到网格将只是添加bet188地址新的UI元素。

该函数由Fabulous调用,并且不直接与Xamarin Forms交互。了解这一点很重要,因为这意味着您编写的所有代码都可以进行100%单元测试。对视图的所有依赖关系都在Fabulous框架内解决。的view函数返回有关如何创建UI的说明,但不会创建它。如果您更改诸如欢迎消息之类的值,则Fabulous Framework将检查哪些部分已更改并相应地更新视图。 React.JS框架对影子DOM(文档对象模型)使用相同的技术,然后将其用于更新实际的UI。

原子设计和编码用户界面

用代码编写UI会带来一些好处。虽然您可以在view功能。随时间推移,乍一看可能会有些困难。但是,仅作为代码,您可以将代码拆分为不同的功能。这也使您可以在不同位置重用UI的各个部分。可重用性重用/组合组件是Atomic Design的核心。

原子设计

独特的设计,可重复使用的组件听起来很棒,'看看我们如何使用Fabulous设计这样的应用程序。我们想从基本元素(原子)开始,然后将它们组合到更重要的UI组件中,最后是Page。

当我们查看应用程序的设计时,我们可以看到大多数标题标签似乎都具有相同的字体。另bet188地址迅速引起我注意的UI组件是持有目的地描述和要做的事情的卡片:

DestinationDescription

现在,我们可以看到标题具有相同的字体,粗体且与"要做的事"部分具有相同的字体大小。所以让's创建bet188地址函数,该函数使我们可以使用参数text和font size创建标题标签:

let titleLabel text fontSize =
    View.Label(text = text,
        fontSize = fontSize,
        textColor = textColor,
        verticalOptions = LayoutOptions.Center,
        fontAttributes = FontAttributes.Bold)

目的地是通过图片(如果我可以这么说的话,是华丽的图片!)以及城镇,国家,等级和收藏夹的简短说明显示的。收藏夹似乎应该可以单击,所以让's是bet188地址按钮。可能类似于右上角的搜索按钮。牢记可访问性,如果用户需要进行交互,那么我通常更喜欢使用按钮或平台交互控件。这样,使用屏幕阅读器可以更轻松地优化体验。因此,我们需要带有图标的按钮-或文本。由于Xamarin Forms允许我们使用自定义字体,因此我们可以使用诸如字体很棒为我们提供可扩展的图标。一定要签出詹姆士'发布有关如何在Xamarin Forms应用程序中使用Font Awesome的信息。所以让's创建bet188地址给定图标,颜色,背景色和命令功能的函数,通过按钮返回给我们:

let materialFont =
    (match Device.RuntimePlatform with
                             | Device.iOS -> "Material Design Icons"
                             | Device.Android -> "materialdesignicons-webfont.ttf#Material Design Icons"
                             | _ -> null)

let materialButton materialIcon backgroundColor textColor command =
    View.Button(text = materialIcon,
        command = command,
        fontFamily = materialFont,
        fontSize = 20.,
        backgroundColor = backgroundColor,
        widthRequest = 42.,
        textColor = textColor)

现在是描述文字,即国家/地区。让我们再次创建bet188地址函数,该函数将在给定文本的情况下创建标签:

let descriptionLabel text =
    View.Label(text = text,
        textColor = secondaryTextColor,
        fontSize = descriptionFontSize
        )

您是否注意到标题和描述模式在"要做的事"页面的部分。到现在为止,我们已经创建了原子设计所称的原子。现在让's将其中一些原子打包成bet188地址相干的块,例如(分子):

let titleAndDescription title titleFontSize description =
    View.StackLayout(margin = 0.,
        children=[
            titleLabel title titleFontSize
            descriptionLabel description |> fun(label) -> label.Margin (Thickness(0.,-8.,0.,0.))]

这将使我们可以重复使用标题&进一步说明。另外,请注意,我们必须稍微调整边距。你可以想到|>作为前进的管道。由于我们具有View类型,因此可以将其通过管道传递给lambda函数,在此我们可以更改边距。调用margin函数将再次返回View类型。如果使用的是LINQ,则很可能已将多个调用加入到select等的位置。 -我们在这里做的完全一样。

现在回头看一下目的地的简短描述,我们还可以看到带有星星的城市等级。所以让's创建bet188地址给定图标和文本颜色的函数,返回bet188地址Label基于字体真棒。

let materialIcon materialIcon color =
    View.Label(text = materialIcon,
        textColor = color,
        fontFamily = materialFont,
        fontSize = 18.,
        verticalOptions = LayoutOptions.Center,
        fontAttributes = FontAttributes.Bold)

评分栏-我认为这是bet188地址只读指示器,可以向我显示总体评分(介于0到5之间)。给定4.5级,我们希望有四颗全星,一颗被一半覆盖。所以让'拆开此控件,让's说我们想要bet188地址仅以一定百分比绘制星星的函数:

let ratingStar percentage =
    let star = materialIcon star starColor
    let boxViewWidth = 16. - (16. * percentage)
    View.Grid(
        padding = 0.,
        margin = Thickness(0.,-4.,0.,0.),
        children = [
            star
            View.BoxView(color = backgroundColor, 
                widthRequest = boxViewWidth,
                isVisible = (if percentage > 0. then true else false),
                horizontalOptions = LayoutOptions.End)
            ])

该函数又名star factory由另bet188地址函数调用,该函数根据给定的等级绘制N个星:

let ratingControl (rating:decimal) =
    let fullNumber = Math.Ceiling(rating)
    let fraction = (rating - Math.Truncate(rating))
    View.StackLayout(orientation = StackOrientation.Horizontal,
        children = [
            for i in 1m .. fullNumber -> if i = fullNumber then ratingStar (float fraction) else ratingStar 1.
        ])

现在,我们将所有构建块放在一起进行描述,但是仍然保留带有圆角的图像。快速浏览ImageViewXamarin Forms告诉我们:"无圆角边缘。"但是当把图像放在bet188地址Frame,我们可以创建圆角的边缘效果。所以让's创建bet188地址函数,为我们提供带有圆角的图像:

let roundedCornerImage imagePath =
    View.Frame(cornerRadius = cornerRadius,
        padding = 0.,
        isClippedToBounds = true,
        hasShadow = true,
        content = View.Image(
            source = imagePath,
            aspect = Aspect.AspectFill)
    )

现在零件全部制成了's组装它们,以使我们得到带有圆角并由简短描述覆盖的Image:

let cityDescriptionFrame city dispatch =
    View.StackLayout(
        margin = Thickness(16.,0.,16.,0.),
        children = [
            (roundedCornerImage city.Image |> fun(img) -> img.HeightRequest 320.)
            View.Frame(
                heightRequest = 70.,
                margin = Thickness(24.,-64.,24.,0.),
                padding = Thickness(20.,12.,16.,12.),
                backgroundColor = Color.White,
                cornerRadius = cornerRadius,
                content = View.Grid(
                    rowdefs=["auto"; "auto" ],
                    coldefs=["*";"auto"],
                    children=[
                        (titleAndDescription city.Name titleFontSize city.Country)
                        (favoriteIcon city dispatch).GridColumn(2)
                        (ratingControl city.Rating).GridRow(1).GridColumnSpan(2)
                        ]
                ),
                hasShadow = true)
        ])

同样,我们可以实现"要做的事"部分。很棒的事情是我们可以重用我们已经创建的许多组件。然后,我们可以将所有部分放到view方法中,该方法为我们提供以下UI:

AppScreenshot

您可以在上找到整个样本的GitHub.

旁注:不,我们不需要将所有代码都放在bet188地址文件中。但是由于这是bet188地址单页应用程序,所以我将其放在一起,因此在浏览器中浏览代码更加容易。进一步注意CarouselView使用视图时无法正常工作。我希望我很快就能开始工作,并有bet188地址样本,该样本可以按设计意图在城市之间进行切换。

结论

将Atomic Design模式应用于您的UI确实可以使您的应用程序更易于维护和创建。鉴于Fabulous允许用代码编写UI,因此无需太多样板代码即可创建自定义且一致的UI相对简单。 More 极好提供了实时更新功能,使您可以在调试会话期间实时编码。 UI不仅可以适应,而且逻辑也可以执行。您可以阅读更多有关 实时更新功能在官方网站上。

在2019年的最近几天,用代码编写UI似乎重新流行起来。Apple之类的公司正在开发Swift UI。如果你是顽固的C#情人,你应该看看Ryan Davis发表用C编写UI#Xamarin。

您可以在下面阅读有关Atomic Design模式的更多信息Brad Frosts网站.

2条留言

显示一辆带有出租车条纹的黄色大众巴士的Wall-E前方

我很喜欢 极好 -Xamarin.Forms的包装,使您可以使用F#编写功能性UI。初次查看项目时,我注意到它是建立在 AppVeyor特拉维斯。我问自己:为什么要使用两个CI系统来编译bet188地址项目?经过进一步的挖掘,我发现AppVeyor上没有托管的macOS代理。另一方面,Travis确实提供了适用于Windows和macOS的代理,但没有在代理上安装Xamarin工具链。每次运行都安装Xamarin工具链会导致30分钟以上的构建时间。以来 Azure开发运营 支持在Windows和macOS上进行构建我想我会尝试一下并设置管道来构建Fabulous-我的意思是这有多难?很难写一篇博客文章来总结克服陷阱的步骤

TLDR:如何在Azure DevOps上运行FAKE脚本

神话般的用途 执行构建,测试和创建NuGet包。 假是用于编写构建脚本的功能强大的完整工具。假也是 .Net Core CLI工具 该工具专为从命令行安装和执行而设计,因此非常适合在任何构建服务器上运行。

安装假

Azure开发运营构建代理不预装有FAKE。由于FAKE是.Net Core CLI工具,因此这没有问题。以下命令应解决此问题:

dotnet tool install fake-cli -g

不幸的是,安装后执行FAKE失败。这是因为Azure DevOps构建代理上的安装目录不同于.Net Core的标准安装位置-为什么?您问,那么给出的答案就是安全性。在Windows上,我们可以通过将FAKE安装到Workspace目录中来避免这一情况:

dotnet tool install fake-cli --tool-path .

在macOS(和Linux)下,这种方法仍然会失败。的 建议的解决方案 is to set DOTNET_ROOT. I ended up with the following lines to be executed on the 苹果系统 agent:

export DOTNET_ROOT=$HOME/.dotnet/
export PATH=$PATH:$HOME/.dotnet/tools:/Library/Frameworks/Mono.framework/Versions/Current/Commands
dotnet tool install fake-cli -g

在Linux上,该方法不得不再次采用-实在是很重要。我结束了这些行:

export PATH=$PATH:$HOME/.dotnet/tools:/Library/Frameworks/Mono.framework/Versions/Current/Commands
dotnet tool install fake-cli -g

现在您应该可以在Azure DevOps上运行FAKE脚本

使用NuGetFallbackDirectory

This part is not directly related to 假 but is something I stumbled over while running on Azure开发运营. One test script was referencing the NuGet packages via the global NuGetFallbackDirectory 和 was looking for them under the default location. Under 苹果系统 the location is in the users home directory, so adopting the path as follows did the trick:

let tfsEnvironment = Environment.GetEnvironmentVariable("TF_BUILD")
if (String.IsNullOrEmpty(tfsEnvironment)) then
    "/usr/local/share/dotnet/sdk/NuGetFallbackFolder"
else
    let homepath = Environment.GetEnvironmentVariable("HOME")
    Path.Combine(homepath, ".dotnet/sdk/NuGetFallbackFolder")

Note that the variable TF_BUILD is expected to only be set on TFS/VSTS/Azure DevOps. This will allow the script to fall back to the default location should it be executed on a developers machine.

但是,为什么还要打扰呢?

从工作中的CI迁移到另bet188地址CI的动机是什么?您是否在做,因为您是Microsoft MVP?

这些是与同事谈论我在Azure DevOps上构建Fabulous的工作时遇到的问题。我认为AppVeyor和Travis是很棒的工具,它们表明他们有能力完成任务和测试Fabulous。除了因为我好奇它的难易程度外,我还想从两个方面尝试将生成版本迁移到Azure DevOps:

  1. 合并构建,让两个地方做一件事情总是伴随着开销。
  2. 另bet188地址人正在看到无需安装Xamarin可以减少多少构建时间。

因此,这是之前和之后的构建时间之间的比较:

CI平台代理操作系统建立测试时间(分钟)
特拉维斯苹果系统~30-32
Azure开发运营苹果系统~13-14
AppVeyor视窗~4
Azure开发运营视窗~6

现在要记住,在macOS和Windows上的构建是并行运行的。因此,对于AppVeyor和Travis而言,生成时间为30-32分钟。借助Azure DevOps,这可以减少到13-14分钟。

我认为将两个构建脚本合并为bet188地址并将构建时间减少大约一半是bet188地址很好的理由,说明为什么Azure DevOps似乎更适合Fabulous。再说一次,运行.Net CLI工具时会遇到一些麻烦,我希望Azure DevOps团队将来会解决-由同一家公司和所有 咳嗽

另bet188地址方面是将来在Linux上进行构建,因为Fabulous自0.30开始支持GTK,因此也可以在Linux上编译它。在撰写本文时,Fabulous的构建过程中仍然存在一些问题,但是将来没有什么不能解决的。

谢谢

谢谢 蒂莫西·拉里维耶(TimothéLarivière)斯图尔特·朗 对于沿途的所有提示和提示