【问题标题】:Adding controls in background thread?在后台线程中添加控件?
【发布时间】:2016-12-12 06:10:12
【问题描述】:

我正在尝试在用户登录后创建一些控件。

我有一个带有 3 个选项卡的 TabControl:第一个用于登录,第二个是需要显示大量图块的地方(MahApps.Metro Tile)。每个图块代表一个酒店房间。

第三个标签显示一个不确定的进度条。当用户登录时,会显示此选项卡。

我是怎么做到的:

    //User is logged in, so display the progress bar and add controls
    tabControl.SelectedIndex = 2; // display the progress bar tab

    private void populateHotel(Hotel hotel)
    {
        List<Room> rooms = hotel.rooms;
        Tile tile;
        tile_wrapper.Children.Clear();
        foreach (Room room in rooms)
        {
            sql.insertHotelRoom(room);
            tile = new Tile();
            tile.Title = room.room_num;
            tile.Content = room.name_fa;              
            tile.FontFamily = titleRooms.FontFamily; // titleRooms is a textbox
            tile.TitleFontSize = item1.TitleFontSize; // item1 is an already created tile
            tile.FontSize = item1.FontSize;
            tile_wrapper.Children.Add(tile);

        }
        // Now display the rooms tab
        tabControl.SelectedIndex = 1;
    }

问题是,这会冻结 UI 线程并且所有内容都会冻结,包括进度条,直到它创建所有图块为止。

如何添加控件以使进度条仍然响应?

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

    您所做的是将插入 Hotel 的业务代码与显示它的 UI 代码混合在一起。混合业务代码和 UI 代码不是一个好主意。除了由此产生的可维护性问题外,它还会导致您的问题,因为 UI 必须等待您的 insertHotelRoom 操作并且无法对用户输入做出反应。

    您应该做的第一件事是将您的关注点分开:

    private List<Room> insertHotelRooms(Hotel hotel)
    {
        List<Room> rooms = hotel.rooms;
        foreach (Room room in rooms)
        {
            sql.insertHotelRoom(room);
            rooms.Add(room);
        }
        return rooms;
    }
    
    
    private void populateHotelTab(List<Room> rooms)
    {
        Tile tile;
        tile_wrapper.Children.Clear();
        foreach (Room room in rooms)
        {
            tile = new Tile();
            tile.Title = room.room_num;
            tile.Content = room.name_fa;              
            tile.FontFamily = titleRooms.FontFamily; // titleRooms is a textbox
            tile.TitleFontSize = item1.TitleFontSize; // item1 is an already created tile
            tile.FontSize = item1.FontSize;
            tile_wrapper.Children.Add(tile);
    
        }
        // Now display the rooms tab
        tabControl.SelectedIndex = 1;
    }
    

    您现在可以在任务中运行操作并将 UI 操作分派回 UI 线程:

    Task insertRoomsTask = Task.Run( () => {
        List<Room> rooms = insertRooms(hotel);
        tile_wrapper.Dispatcher.Invoke(() => populateHotelTab(rooms));
    });
    

    【讨论】:

    • 作为测试,如果我在 populateHotelTab 的 foreach 循环中添加一个 Thread.Sleep(1000),UI 是否仍然响应?我这样做了,但进度条仍然冻结。
    • @SMahdiS:在哪个foreach 循环中?如果在populateHotelTab 中添加Thread.Sleep,那么UI 仍然会冻结。如果您冻结 UI 线程,这是预期的行为。但你为什么要这样做?该解决方案的重点是在 UI 线程之外执行耗时的工作。并且不推荐在任务中使用Thread.Sleep,因为你不知道你在哪个调度器上运行。
    • 我学到了很多,谢谢。顺便说一句,那东西是什么:“()=> {}”。是代表吗?如果没有,它是什么,我应该用谷歌搜索什么?我习惯了 Java 编程,这些东西对我来说似乎很新鲜。
    • @SMahdiS:那是一个lambda表达式,相当于一个委托。
    • 它仍然有一些延迟和冻结。我认为这是由于:tile_wrapper.Children.Add(tile);这是不可避免的吗?
    【解决方案2】:
    //User is logged in, so display the progress bar and add controls
    tabControl.SelectedIndex = 2; // display the progress bar tab
    
    private void populateHotel(Hotel hotel)
    {
        List<Room> rooms = hotel.rooms;
        Tile tile;
        tile_wrapper.Children.Clear();
    
        // Use a count variable to keep track of how many rooms have been processed
        int count = 0;
    
        foreach (Room room in rooms)
        {
            // Update the value of the progress bar
            progressbar1.Value = count++ * 100 / rooms.Count;
            // Cause the progress bar to refresh
            progressbar1.Refresh();
    
            sql.insertHotelRoom(room);
            tile = new Tile();
            tile.Title = room.room_num;
            tile.Content = room.name_fa;              
            tile.FontFamily = titleRooms.FontFamily; // titleRooms is a textbox
            tile.TitleFontSize = item1.TitleFontSize; // item1 is an already created tile
            tile.FontSize = item1.FontSize;
            tile_wrapper.Children.Add(tile);
    
        }
        // Now display the rooms tab
        tabControl.SelectedIndex = 1;
    }
    

    【讨论】:

    • 不,进度条是一个不确定的 MetroProgressBar 并且没有 Refresh() 方法。虽然它有一个 UpdateLayout() 但这也没有用。
    • 在名为 populateHotel() 的函数中包含 UI 逻辑并不理想。您可以使用后台工作人员执行 SQL 插入 (msdn.microsoft.com/fr-fr/library/cc221403(v=vs.95).aspx),但首先我会尝试将您的应用程序转换为使用异步调用。您很可能不需要手动处理线程,并且可以依靠异步回调为主 UI 线程提供足够的时间来保持一切响应。
    • 不,不是。我想创建一个在每个房间更新时调用的委托会更好,第三个选项卡可以分配给它。然后,您可以在与 UI 不同的线程上运行 populateHotel()。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-24
    • 1970-01-01
    相关资源
    最近更新 更多