Creating a New Window Group

本文介绍如何在NetBeans平台中使用窗口组来统一管理多个组件的打开和关闭状态,通过配置XML文件实现TopComponent的自动同步,并利用自定义动作来触发这些状态变更。

In the IDE, when you're involved in GUI editing, several helper windows open together with the Design mode of the editor. For example, when you open a TopComponent in Design mode, the Palette and the Properties window open too. However, if you had previously opened the TopComponent in Design mode and closed the Palette, the Palette isn't opened when next you open the TopComponent. Basically, the helper windows (i.e., in this case, the Palette and Properties window) are only open if you had them open previously, on the assumption that their previous open/closed state are the ones you would like to maintain.

If you had to code that for all your windows, it would be something like this in pseudo code:

if (TopComponentA is opened) {
if (HelperTopComponentB was open previously and is now closed) {
HelperTopComponentB must open again
}
if (HelperTopComponentC was open previously and is now closed) {
HelperTopComponentC must open again
}
}

That would be a lot of cumbersome work, since the above is only for the opening of the TopComponent; there'd have to be something similar for closing the TopComponent. Wouldn't it be cool if I could create a group of components and then simply do this:

group.open();

And, then, later, when I close a TopComponent, I would simply call this:

group.close();

Then, all the opened and closed helper windows would be saved in whatever state they're in, automatically, without me having to think about it. Now, that would be cool. Hmmm... I think I should create an issue in Issuezilla for this. Okay, let's do that. Oops. Wait. Section 2.3 of the "New Window System API Changes" is titled "Window Groups". That's, in fact, exactly what I was looking for... So, three cheers for Interface TopComponentGroup .

So, if you go to config/Windows2Local in your user directory, you should see (after closing the IDE at least once) the following:

In the previous two blog entries, I wrote about the "Modes" folder above. The first blog entry was about creating a new mode. The second was about two modes sharing the 'editor' area of the IDE (or of another application based on the NetBeans Platform). This time, let's look at the "Groups" folder. If you open the "Groups" folder, one of the subfolders is called "debugger". It contains files for 9 TopComponents, which are all opened at the same time when a debug process begins and closed at the same time when it ends.

Let's create our own group, add two TopComponents, and then open the TopComponents simultaneously.

  1. Create a new module project, with org.netbeans.modules.windowgroupsample as the code name base.
  2. Use the Window Component wizard twice, to create two TopComponents. In the first page of the wizard, choose "editor" for the first and "output" for the second (or anything else, it really doesn't matter). Make sure that you don't select the checkbox, so that the TopComponent won't be shown at start up. Let's say the first is called "OneTopComponent" and the second "TwoTopComponent" (which means you should type "One" and "Two" as the prefix in the wizard) and we'll put them both in the main package.
  3. Now we're going to create the window group. Right-click the top package, create a new XML document called "MyGroupWsgrp.xml", within a new subpackage called "groups". Add a subpackage to the new group and call that subpackage "MyGroup". Inside of it, create two XML documents, one called "OneTopComponentWstcgrp.xml" and the other "TwoTopComponentWstcgrp.xml". You should now see this in the Projects window:

  4. Next, put this in "MyGroupWsgrp.xml":
    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE group PUBLIC
    "-//NetBeans//DTD Group Properties 2.0//EN"
    "http://www.netbeans.org/dtds/group-properties2_0.dtd">

    <group version="2.0">
    <module name="org.netbeans.modules.windowgroupsample" spec="1.0" />
    <name unique="MyGroup" />
    <state opened="false" />
    </group>

    Note: The value of the state element above specifies that the group will be closed, by default, when the application starts up.

  5. In "OneTopComponentWstcgrp.xml", change the content to this:
    <?xml version="1.0" encoding="UTF-8" ?>

    <!DOCTYPE tc-group PUBLIC
    "-//NetBeans//DTD Top Component in Group Properties 2.0//EN"
    "http://www.netbeans.org/dtds/tc-group2_0.dtd">

    <tc-group version="2.0">
    <module name="org.netbeans.modules.windowgroupsample" spec="1.0"/>
    <tc-id id="OneTopComponent" />
    <open-close-behavior open="true" close="true" />
    </tc-group>

    Note 1: The value of the tc-id element must match the value of the PREFERRED_ID String that was generated in your TopComponent, when you finished the Window Component wizard. Have a look, and notice that the two match.

    Note 2: The values of the open-close-behavior element are the flags that indicate what will happen when group.open() and group.close() are called. For example, if the open attribute is set to "true", then by default the TopComponent will open when the group opens.

    Similar to the above, change the content of "TwoTopComponentWstcgrp.xml" to this:

    <?xml version="1.0" encoding="UTF-8" ?>

    <!DOCTYPE tc-group PUBLIC
    "-//NetBeans//DTD Top Component in Group Properties 2.0//EN"
    "http://www.netbeans.org/dtds/tc-group2_0.dtd">

    <tc-group version="2.0">
    <module name="org.netbeans.modules.windowgroupsample" spec="1.0"/>
    <tc-id id="TwoTopComponent" />
    <open-close-behavior open="true" close="true" />
    </tc-group>
  6. Now we will register our new group in the XML Layer. Open the XML Layer and notice the Windows2 section at the end (all generated when you created your two TopComponents). Add the highlighted section below, to register our new group:
    <folder name="Windows2">
    <folder name="Components">
    <file name="OneTopComponent.settings" url="OneTopComponentSettings.xml"/>
    <file name="TwoTopComponent.settings" url="TwoTopComponentSettings.xml"/>
    </folder>
    <folder name="Modes">
    <folder name="editor">
    <file name="OneTopComponent.wstcref" url="OneTopComponentWstcref.xml"/>
    </folder>
    <folder name="output">
    <file name="TwoTopComponent.wstcref" url="TwoTopComponentWstcref.xml"/>
    </folder>
    </folder>
    <folder name="Groups">
    <file name="MyGroup.wsgrp" url="groups/MyGroupWsgrp.xml"/>
    <folder name="MyGroup">
    <file name="OneTopComponent.wstcgrp" url="groups/MyGroup/OneTopComponentWstcgrp.xml"/>
    <file name="TwoTopComponent.wstcgrp" url="groups/MyGroup/TwoTopComponentWstcgrp.xml"/>
    </folder>
    </folder>

    </folder>
  7. Save everything. Don't install the module yet, let's first refresh all our window positions to their defaults (just in case you've moved things around and things go wrong later, best to have everything at their defaults so that we can analyze the situation better). Close the IDE. Go to the user directory and delete the Windows2Local folder in the user directory's config folder.
  8. Start the IDE again. Install the module in the IDE. Close the IDE. Go back to the Windows2Local folder and, when you open the Groups folder, you should now see your new group definition file as well as a folder, containing a file for each of the two TopComponents that belongs to the group (according to your registration entries in the XML Layer):

  9. Now start the IDE again. Use the New Action wizard twice. The first time, create "ShowMyGroupAction" and stick this in the performAction() event:
    TopComponentGroup group = WindowManager.getDefault().findTopComponentGroup("MyGroup");
    if (group == null) {
    return;
    }
    group.open();

    Put the cursor on the first line above and, when the lightbulb appears, let the IDE generate import statements for these packages:

    import org.openide.windows.TopComponentGroup;
    import org.openide.windows.WindowManager;

    The second time you use the New Action wizard, create "HideMyGroupAction" and stick the following into the performAction() event:

    TopComponentGroup group = WindowManager.getDefault().findTopComponentGroup("MyGroup");
    if (group == null) {
    return;
    }
    group.close();

    Again let the IDE generate import statements for the two required packages.

  10. Install the module again. Now you can use the menu items to show and hide both TopComponents simultaneously. There's a lot of variations that apply here. If you close one of them after opening both, it will not be opened next time you use the menu item for showing both. And that's only one example of the way the Window System API now does all the thinking for you.

本文转自 Geertjan 的博客,原文地址:http://blogs.sun.com/geertjan/entry/creating_a_window_group

chromium源码中下面的函数具体功能,详细解释一下: base::WeakPtr<content::NavigationHandle> Navigate(NavigateParams* params) { TRACE_EVENT1("navigation", "chrome::Navigate", "disposition", params->disposition); Browser* source_browser = params->browser; if (source_browser) { params->initiating_profile = source_browser->profile(); } DCHECK(params->initiating_profile); // If the created window is a partitioned popin, a valid source exists, and // the disposition is NEW_POPUP then the resulting popup should be tab-modal. // See: https://explainers-by-googlers.github.io/partitioned-popins/ params->is_tab_modal_popup |= params->window_features.is_partitioned_popin && params->source_contents && params->disposition == WindowOpenDisposition::NEW_POPUP; #if BUILDFLAG(IS_CHROMEOS) if (params->initiating_profile->IsOffTheRecord() && params->initiating_profile->GetOTRProfileID().IsCaptivePortal() && params->disposition != WindowOpenDisposition::NEW_POPUP && params->disposition != WindowOpenDisposition::CURRENT_TAB && !IncognitoModeForced(params->initiating_profile)) { // Navigation outside of the current tab or the initial popup window from a // captive portal signin window should be prevented. params->disposition = WindowOpenDisposition::CURRENT_TAB; } #endif if (params->initiating_profile->ShutdownStarted()) { // Don't navigate when the profile is shutting down. return nullptr; } // Block navigation requests when in locked fullscreen mode. We allow // navigation requests in the webapp when locked for OnTask (only relevant for // non-web browser scenarios). // TODO(b/365146870): Remove once we consolidate locked fullscreen with // OnTask. if (source_browser) { bool should_block_navigation = platform_util::IsBrowserLockedFullscreen(source_browser); #if BUILDFLAG(IS_CHROMEOS) if (source_browser->IsLockedForOnTask()) { should_block_navigation = false; } #endif // BUILDFLAG(IS_CHROMEOS) if (should_block_navigation) { return nullptr; } } // Open System Apps in their standalone window if necessary. // TODO(crbug.com/40136163): Remove this code after we integrate with intent // handling. #if BUILDFLAG(IS_CHROMEOS) const std::optional<ash::SystemWebAppType> capturing_system_app_type = ash::GetCapturingSystemAppForURL(params->initiating_profile, params->url); if (capturing_system_app_type && (!params->browser || !ash::IsBrowserForSystemWebApp(params->browser, capturing_system_app_type.value()))) { ash::SystemAppLaunchParams swa_params; swa_params.url = params->url; ash::LaunchSystemWebAppAsync(params->initiating_profile, capturing_system_app_type.value(), swa_params); // It's okay to early return here, because LaunchSystemWebAppAsync uses a // different logic to choose (and create if necessary) a browser window for // system apps. // // It's okay to skip the checks and cleanups below. The link captured system // app will either open in its own browser window, or navigate an existing // browser window exclusively used by this app. For the initiating browser, // the navigation should appear to be cancelled. return nullptr; } #endif // BUILDFLAG(IS_CHROMEOS) #if !BUILDFLAG(IS_ANDROID) // Force isolated PWAs to open in an app window. params->force_open_pwa_window = content::SiteIsolationPolicy::ShouldUrlUseApplicationIsolationLevel( params->initiating_profile, params->url); params->open_pwa_window_if_possible |= params->force_open_pwa_window; #endif if (!AdjustNavigateParamsForURL(params)) { return nullptr; } // Picture-in-picture browser windows must have a source contents in order for // the window to function correctly. If we have no source contents to work // with (e.g. if an extension popup attempts to open a PiP window), we should // cancel the navigation. The source URL must also be of a type that's // allowed to open document PiP. See `PictureInPictureWindowManager` for // details on what's allowed. if (params->disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) { const GURL& url = params->source_contents ? params->source_contents->GetLastCommittedURL() : GURL(); if (!PictureInPictureWindowManager::IsSupportedForDocumentPictureInPicture( url)) { return nullptr; } } // If no source WebContents was specified, we use the selected one from the // target browser. This must happen before GetBrowserAndTabForDisposition() // has a chance to replace |params->browser| with another one, but after the // above check that relies on the original source_contents value. if (!params->source_contents && params->browser) { params->source_contents = params->browser->tab_strip_model()->GetActiveWebContents(); } WebContents* contents_to_navigate_or_insert = params->contents_to_insert.get(); if (params->switch_to_singleton_tab) { DCHECK_EQ(params->disposition, WindowOpenDisposition::SINGLETON_TAB); contents_to_navigate_or_insert = params->switch_to_singleton_tab; } #if !BUILDFLAG(IS_ANDROID) // If this is a Picture in Picture window, then notify the pip manager about // it. This enables the opener and pip window to stay connected, so that (for // example), the pip window does not outlive the opener. // // We do this before creating the browser window, so that the browser can talk // to the PictureInPictureWindowManager. Otherwise, the manager has no idea // that there's a pip window. if (params->disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) { // Picture in picture windows may not be opened by other picture in // picture windows, or without an opener. if (!params->browser || params->browser->is_type_picture_in_picture()) { params->browser = nullptr; return nullptr; } PictureInPictureWindowManager::GetInstance()->EnterDocumentPictureInPicture( params->source_contents, contents_to_navigate_or_insert); } #endif // !BUILDFLAG(IS_ANDROID) // TODO(crbug.com/364657540): Revisit integration with web_application system // later if needed. int singleton_index; #if !BUILDFLAG(IS_ANDROID) std::unique_ptr<web_app::NavigationCapturingProcess> app_navigation = web_app::NavigationCapturingProcess::MaybeHandleAppNavigation(*params); std::optional<std::tuple<Browser*, int>> app_browser_tab_override; if (app_navigation) { app_browser_tab_override = app_navigation->GetInitialBrowserAndTabOverrideForNavigation(*params); } std::tie(params->browser, singleton_index) = app_browser_tab_override.has_value() ? *app_browser_tab_override : GetBrowserAndTabForDisposition(*params); #else // !BUILDFLAG(IS_ANDROID) std::tie(params->browser, singleton_index) = GetBrowserAndTabForDisposition(*params); #endif if (!params->browser) { return nullptr; } // Trying to open a background tab when in a non-tabbed app browser results in // focusing a regular browser window and opening a tab in the background // of that window. Change the disposition to NEW_FOREGROUND_TAB so that // the new tab is focused. if (source_browser && source_browser->is_type_app() && params->disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB && !(source_browser->app_controller() && source_browser->app_controller()->has_tab_strip())) { params->disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; } if (singleton_index != -1) { contents_to_navigate_or_insert = params->browser->tab_strip_model()->GetWebContentsAt(singleton_index); } else if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB) { // The user is trying to open a tab that no longer exists. If we open a new // tab, it could leave orphaned NTPs around, but always overwriting the // current tab could could clobber state that the user was trying to // preserve. Fallback to the behavior used for singletons: overwrite the // current tab if it's the NTP, otherwise open a new tab. params->disposition = WindowOpenDisposition::SINGLETON_TAB; ShowSingletonTabOverwritingNTP(params); return nullptr; } if (params->force_open_pwa_window) { CHECK(web_app::AppBrowserController::IsWebApp(params->browser)); } #if BUILDFLAG(IS_CHROMEOS) if (source_browser && source_browser != params->browser) { // When the newly created browser was spawned by a browser which visits // another user's desktop, it should be shown on the same desktop as the // originating one. (This is part of the desktop separation per profile). auto* window_manager = MultiUserWindowManagerHelper::GetWindowManager(); // Some unit tests have no client instantiated. if (window_manager) { aura::Window* src_window = source_browser->window()->GetNativeWindow(); aura::Window* new_window = params->browser->window()->GetNativeWindow(); const AccountId& src_account_id = window_manager->GetUserPresentingWindow(src_window); if (src_account_id != window_manager->GetUserPresentingWindow(new_window)) { // Once the window gets presented, it should be shown on the same // desktop as the desktop of the creating browser. Note that this // command will not show the window if it wasn't shown yet by the // browser creation. window_manager->ShowWindowForUser(new_window, src_account_id); } } } #endif // Navigate() must not return early after this point. if (GetSourceProfile(params) != params->browser->profile()) { // A tab is being opened from a link from a different profile, we must reset // source information that may cause state to be shared. params->opener = nullptr; params->source_contents = nullptr; params->source_site_instance = nullptr; params->referrer = content::Referrer(); } // Make sure the Browser is shown if params call for it. ScopedBrowserShower shower(params, &contents_to_navigate_or_insert); if (params->is_tab_modal_popup) { shower.set_source_contents(params->source_contents); } // Some dispositions need coercion to base types. NormalizeDisposition(params); // If a new window has been created, it needs to be shown. if (params->window_action == NavigateParams::NO_ACTION && source_browser != params->browser && params->browser->tab_strip_model()->empty()) { params->window_action = NavigateParams::SHOW_WINDOW; } // If we create a popup window from a non user-gesture, don't activate it. if (params->window_action == NavigateParams::SHOW_WINDOW && params->disposition == WindowOpenDisposition::NEW_POPUP && params->user_gesture == false) { params->window_action = NavigateParams::SHOW_WINDOW_INACTIVE; } // Determine if the navigation was user initiated. If it was, we need to // inform the target WebContents, and we may need to update the UI. bool user_initiated = params->transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR || !ui::PageTransitionIsWebTriggerable(params->transition); base::WeakPtr<content::NavigationHandle> navigation_handle; std::unique_ptr<tabs::TabModel> tab_to_insert; if (params->contents_to_insert) { tab_to_insert = std::make_unique<tabs::TabModel>(std::move(params->contents_to_insert), params->browser->tab_strip_model()); } // If no target WebContents was specified (and we didn't seek and find a // singleton), we need to construct one if we are supposed to target a new // tab. if (!contents_to_navigate_or_insert) { DCHECK(!params->url.is_empty()); if (params->disposition != WindowOpenDisposition::CURRENT_TAB) { tab_to_insert = std::make_unique<tabs::TabModel>( CreateTargetContents(*params, params->url), params->browser->tab_strip_model()); contents_to_navigate_or_insert = tab_to_insert->GetContents(); apps::SetAppIdForWebContents(params->browser->profile(), contents_to_navigate_or_insert, params->app_id); #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) captive_portal::CaptivePortalTabHelper::FromWebContents( contents_to_navigate_or_insert) ->set_window_type(params->captive_portal_window_type); #endif } else { // ... otherwise if we're loading in the current tab, the target is the // same as the source. DCHECK(params->source_contents); contents_to_navigate_or_insert = params->source_contents; } // Try to handle non-navigational URLs that popup dialogs and such, these // should not actually navigate. if (!HandleNonNavigationAboutURL(params->url)) { // Perform the actual navigation, tracking whether it came from the // renderer. navigation_handle = LoadURLInContents(contents_to_navigate_or_insert, params->url, params); } } else { // |contents_to_navigate_or_insert| was specified non-NULL, and so we assume // it has already been navigated appropriately. We need to do nothing more // other than add it to the appropriate tabstrip. } // If the user navigated from the omnibox, and the selected tab is going to // lose focus, then make sure the focus for the source tab goes away from the // omnibox. if (params->source_contents && (params->disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB || params->disposition == WindowOpenDisposition::NEW_WINDOW) && (params->tabstrip_add_types & AddTabTypes::ADD_INHERIT_OPENER)) { params->source_contents->Focus(); } if (tab_to_insert) { // Save data needed for link capturing into apps that cannot otherwise be // inferred later in the navigation. These are only needed when the // navigation happens in a different tab to the link click. apps::SetLinkCapturingSourceDisposition(tab_to_insert->GetContents(), params->disposition); } if (params->source_contents == contents_to_navigate_or_insert) { // The navigation occurred in the source tab. params->browser->UpdateUIForNavigationInTab( contents_to_navigate_or_insert, params->transition, params->window_action, user_initiated); } else if (singleton_index == -1) { if (source_browser != params->browser) { params->tabstrip_index = params->browser->tab_strip_model()->count(); } // If some non-default value is set for the index, we should tell the // TabStripModel to respect it. if (params->tabstrip_index != -1) { params->tabstrip_add_types |= AddTabTypes::ADD_FORCE_INDEX; } // Maybe notify that an open operation has been done from a gesture. // TODO(crbug.com/40719979): preferably pipe this information through the // TabStripModel instead. See bug for deeper discussion. if (params->user_gesture && source_browser == params->browser) { params->browser->window()->LinkOpeningFromGesture(params->disposition); } DCHECK(tab_to_insert); // The navigation should insert a new tab into the target Browser. params->browser->tab_strip_model()->AddTab( std::move(tab_to_insert), params->tabstrip_index, params->transition, params->tabstrip_add_types, params->group); } if (singleton_index >= 0) { // If switching browsers, make sure it is shown. if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB && params->browser != source_browser) { params->window_action = NavigateParams::SHOW_WINDOW; } if (contents_to_navigate_or_insert->IsCrashed()) { contents_to_navigate_or_insert->GetController().Reload( content::ReloadType::NORMAL, true); } else if (params->path_behavior == NavigateParams::IGNORE_AND_NAVIGATE && contents_to_navigate_or_insert->GetURL() != params->url) { navigation_handle = LoadURLInContents(contents_to_navigate_or_insert, params->url, params); } // If the singleton tab isn't already selected, select it. if (params->source_contents != contents_to_navigate_or_insert) { // Use the index before the potential close below, because it could // make the index refer to a different tab. auto gesture_type = user_initiated ? TabStripUserGestureDetails::GestureType::kOther : TabStripUserGestureDetails::GestureType::kNone; bool should_close_this_tab = false; if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB) { // Close orphaned NTP (and the like) with no history when the user // switches away from them. if (params->source_contents) { if (params->source_contents->GetController().CanGoBack() || (params->source_contents->GetLastCommittedURL().spec() != chrome::kChromeUINewTabURL && params->source_contents->GetLastCommittedURL().spec() != url::kAboutBlankURL)) { // Blur location bar before state save in ActivateTabAt() below. params->source_contents->Focus(); } else { should_close_this_tab = true; } } } params->browser->tab_strip_model()->ActivateTabAt( singleton_index, TabStripUserGestureDetails(gesture_type)); // Close tab after switch so index remains correct. if (should_close_this_tab) { params->source_contents->Close(); } } } params->navigated_or_inserted_contents = contents_to_navigate_or_insert; // At this point, the `params->navigated_or_inserted_contents` is guaranteed to // be non null, so perform tasks if the navigation has been captured by a web // app, like enqueueing launch params. #if !BUILDFLAG(IS_ANDROID) if (app_navigation) { web_app::NavigationCapturingProcess::AfterWebContentsCreation( std::move(app_navigation), *params->navigated_or_inserted_contents, navigation_handle.get()); } #endif // !BUILDFLAG(IS_ANDROID) return navigation_handle; }
08-01
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值